From 62013099d74bd830bbc00d89111189356d83da3d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 2 Jan 2023 21:00:37 +0100 Subject: [PATCH 01/27] Remove deprecated password wordlists created by default --- rekono/resources/fixtures/1_wordlists.json | 92 +--------------------- 1 file changed, 2 insertions(+), 90 deletions(-) diff --git a/rekono/resources/fixtures/1_wordlists.json b/rekono/resources/fixtures/1_wordlists.json index 5ccfa8c76..dc97a819e 100644 --- a/rekono/resources/fixtures/1_wordlists.json +++ b/rekono/resources/fixtures/1_wordlists.json @@ -2,94 +2,6 @@ { "model": "resources.wordlist", "pk": 1, - "fields": { - "name": "Rockyou", - "type": "Password", - "path": "/usr/share/wordlists/rockyou.txt", - "checksum": null, - "creator": null - } - }, - { - "model": "resources.wordlist", - "pk": 2, - "fields": { - "name": "Most used passwords 2020", - "type": "Password", - "path": "/usr/share/seclists/Passwords/2020-200_most_used_passwords.txt", - "checksum": null, - "creator": null - } - }, - { - "model": "resources.wordlist", - "pk": 3, - "fields": { - "name": "Most 10k common passwords", - "type": "Password", - "path": "/usr/share/seclists/Passwords/Common-Credentials/10k-most-common.txt", - "checksum": null, - "creator": null - } - }, - { - "model": "resources.wordlist", - "pk": 4, - "fields": { - "name": "Top 500 worst passwords", - "type": "Password", - "path": "/usr/share/seclists/Passwords/Common-Credentials/500-worst-passwords.txt", - "checksum": null, - "creator": null - } - }, - { - "model": "resources.wordlist", - "pk": 5, - "fields": { - "name": "Top 10 Dark Web 2017", - "type": "Password", - "path": "/usr/share/seclists/Passwords/darkweb2017-top10.txt", - "checksum": null, - "creator": null - } - }, - { - "model": "resources.wordlist", - "pk": 6, - "fields": { - "name": "Top 100 Dark Web 2017", - "type": "Password", - "path": "/usr/share/seclists/Passwords/darkweb2017-top100.txt", - "checksum": null, - "creator": null - } - }, - { - "model": "resources.wordlist", - "pk": 7, - "fields": { - "name": "Top 1000 Dark Web 2017", - "type": "Password", - "path": "/usr/share/seclists/Passwords/darkweb2017-top1000.txt", - "checksum": null, - "creator": null - } - }, - { - "model": "resources.wordlist", - "pk": 8, - "fields": { - "name": "Top 10000 Dark Web 2017", - "type": "Password", - "path": "/usr/share/seclists/Passwords/darkweb2017-top10000.txt", - "checksum": null, - "creator": null - } - }, - { - "model": "resources.wordlist", - "pk": 9, "fields": { "name": "Common endpoints", "type": "Endpoint", @@ -100,7 +12,7 @@ }, { "model": "resources.wordlist", - "pk": 10, + "pk": 2, "fields": { "name": "Big endpoints list", "type": "Endpoint", @@ -111,7 +23,7 @@ }, { "model": "resources.wordlist", - "pk": 11, + "pk": 3, "fields": { "name": "Backdoor Web Shells", "type": "Endpoint", From 73686d84aa3339f22ad20e8e051cf36cb3b81d58 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 2 Jan 2023 21:02:46 +0100 Subject: [PATCH 02/27] New wordlist type subdomain --- rekono/frontend/src/backend/RekonoApi.vue | 2 +- rekono/frontend/src/components/Wordlists.vue | 2 +- rekono/resources/enums.py | 1 + rekono/testing/api/test_resources.py | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rekono/frontend/src/backend/RekonoApi.vue b/rekono/frontend/src/backend/RekonoApi.vue index b477bfd47..22fffbae1 100644 --- a/rekono/frontend/src/backend/RekonoApi.vue +++ b/rekono/frontend/src/backend/RekonoApi.vue @@ -78,7 +78,7 @@ export default { cancellableStatuses: ['Requested', 'Running'], timeUnits: ['Weeks', 'Days', 'Hours', 'Minutes'], authenticationTypes: ['None', 'Basic', 'Bearer', 'Cookie', 'Digest', 'JWT', 'NTLM'], - wordlistTypes: ['Endpoint'], + wordlistTypes: ['Endpoint', 'Subdomain'], nameRegex: /^[\wÀ-ÿ\s.\-[\]()]{0,100}$/, textRegex: /^[\wÀ-ÿ\s.:,+\-'"?¿¡!#%$€[\]()]{0,300}$/, cveRegex: /^CVE-\d{4}-\d{1,7}$/, diff --git a/rekono/frontend/src/components/Wordlists.vue b/rekono/frontend/src/components/Wordlists.vue index aa2984e4a..677885bce 100644 --- a/rekono/frontend/src/components/Wordlists.vue +++ b/rekono/frontend/src/components/Wordlists.vue @@ -67,7 +67,7 @@ export default { watch: { data () { this.filters = [ - { name: 'Type', values: ['Endpoint', 'Password'], valueField: 'value', textField: 'value', filterField: 'type' }, + { name: 'Type', values: ['Endpoint', 'Subdomain'], valueField: 'value', textField: 'value', filterField: 'type' }, { name: 'Max Size', filterField: 'size__lte', type: 'number' }, { name: 'Creator', filterField: 'creator__username__icontains', type: 'text' }, { name: 'Favourities', values: [{ value: true, text: 'True' }, { value: false, text: 'False' }], valueField: 'value', textField: 'text', filterField: 'liked' } diff --git a/rekono/resources/enums.py b/rekono/resources/enums.py index c8354fda2..7ff2a2810 100644 --- a/rekono/resources/enums.py +++ b/rekono/resources/enums.py @@ -5,3 +5,4 @@ class WordlistType(models.TextChoices): '''Wordlist type names.''' ENDPOINT = 'Endpoint' + SUBDOMAIN = 'Subdomain' diff --git a/rekono/testing/api/test_resources.py b/rekono/testing/api/test_resources.py index 398d4a1b5..8ccf0f108 100644 --- a/rekono/testing/api/test_resources.py +++ b/rekono/testing/api/test_resources.py @@ -54,7 +54,7 @@ def create_wordlist(self, name: str, path: str, type: str, status_code: int = 20 def test_create(self) -> None: '''Test wordlist creation feature.''' # Create new wordlist - new_wordlist = self.create_wordlist(self.name + self.name, self.endpoints, 'Endpoint') + new_wordlist = self.create_wordlist(self.name + self.name, self.endpoints, WordlistType.ENDPOINT) content = self.api_test(self.client.get, f'{self.endpoint}?o=-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) @@ -73,7 +73,7 @@ def test_invalid_create(self) -> None: def test_update(self) -> None: '''Test wordlist update feature.''' - data = {'name': self.name, 'type': 'Endpoint'} + 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) From 38950b3f3f4dddfbfc3ac255b47b8a504913a625 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Mon, 2 Jan 2023 21:06:06 +0100 Subject: [PATCH 03/27] Remove unzipping of rockyou wordlist --- docker/kali/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/kali/Dockerfile b/docker/kali/Dockerfile index b8d2cc235..76f882e08 100644 --- a/docker/kali/Dockerfile +++ b/docker/kali/Dockerfile @@ -26,7 +26,6 @@ RUN pip install -r /opt/spring4shell-scan/requirements.txt RUN pip install emailfinder ssh-audit RUN apt install wordlists seclists dirb -y -RUN gzip -d /usr/share/wordlists/rockyou.txt.gz RUN adduser --disabled-password rekono RUN chown -R rekono:rekono /code From 959b759084fbaeb0787ae2a3ff1515578fbd0abc Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 3 Jan 2023 15:22:05 +0100 Subject: [PATCH 04/27] Remove installation of wordlists package --- docker/kali/Dockerfile | 2 +- rekono/resources/fixtures/1_wordlists.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/kali/Dockerfile b/docker/kali/Dockerfile index 76f882e08..1bc4cb76b 100644 --- a/docker/kali/Dockerfile +++ b/docker/kali/Dockerfile @@ -25,7 +25,7 @@ RUN pip install -r /opt/log4j-scan/requirements.txt RUN pip install -r /opt/spring4shell-scan/requirements.txt RUN pip install emailfinder ssh-audit -RUN apt install wordlists seclists dirb -y +RUN apt install seclists dirb -y RUN adduser --disabled-password rekono RUN chown -R rekono:rekono /code diff --git a/rekono/resources/fixtures/1_wordlists.json b/rekono/resources/fixtures/1_wordlists.json index dc97a819e..e1b16e340 100644 --- a/rekono/resources/fixtures/1_wordlists.json +++ b/rekono/resources/fixtures/1_wordlists.json @@ -5,7 +5,7 @@ "fields": { "name": "Common endpoints", "type": "Endpoint", - "path": "/usr/share/wordlists/dirb/common.txt", + "path": "/usr/share/dirb/wordlists/common.txt", "checksum": null, "creator": null } @@ -16,7 +16,7 @@ "fields": { "name": "Big endpoints list", "type": "Endpoint", - "path": "/usr/share/wordlists/dirb/big.txt", + "path": "/usr/share/dirb/wordlists/big.txt", "checksum": null, "creator": null } From 89d12b3d82dbee14c41c6773201106c9920c0e1d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 3 Jan 2023 21:38:11 +0100 Subject: [PATCH 05/27] Add support for Gobuster tool --- .secrets.baseline | 4 +- rekono/findings/enums.py | 1 + rekono/testing/data/reports/gobuster/dir.txt | 13 +++ rekono/testing/data/reports/gobuster/dns.txt | 2 + .../testing/data/reports/gobuster/vhost.txt | 23 +++++ rekono/testing/tools/test_gobuster.py | 59 +++++++++++++ rekono/tools/fixtures/1_tools.json | 13 +++ rekono/tools/fixtures/2_intensities.json | 45 ++++++++++ rekono/tools/fixtures/3_configurations.json | 30 +++++++ rekono/tools/fixtures/4_arguments.json | 88 +++++++++++++++++++ rekono/tools/fixtures/5_inputs.json | 80 +++++++++++++++++ rekono/tools/fixtures/6_outputs.json | 24 +++++ rekono/tools/tools/cmseek.py | 2 +- rekono/tools/tools/dirsearch.py | 2 +- rekono/tools/tools/emailfinder.py | 2 +- rekono/tools/tools/emailharvester.py | 2 +- rekono/tools/tools/gitleaks.py | 2 +- rekono/tools/tools/gobuster.py | 63 +++++++++++++ rekono/tools/tools/joomscan.py | 2 +- rekono/tools/tools/log4j_scan.py | 2 +- rekono/tools/tools/metasploit.py | 2 +- rekono/tools/tools/nikto.py | 2 +- rekono/tools/tools/nmap.py | 2 +- rekono/tools/tools/nuclei.py | 2 +- rekono/tools/tools/searchsploit.py | 2 +- rekono/tools/tools/smbmap.py | 2 +- rekono/tools/tools/spring4shell_scan.py | 2 +- rekono/tools/tools/ssh_audit.py | 2 +- rekono/tools/tools/sslscan.py | 2 +- rekono/tools/tools/sslyze.py | 2 +- rekono/tools/tools/theharvester.py | 5 +- rekono/tools/tools/zap.py | 2 +- rekono/tools/utils.py | 2 +- 33 files changed, 465 insertions(+), 23 deletions(-) create mode 100644 rekono/testing/data/reports/gobuster/dir.txt create mode 100644 rekono/testing/data/reports/gobuster/dns.txt create mode 100644 rekono/testing/data/reports/gobuster/vhost.txt create mode 100644 rekono/testing/tools/test_gobuster.py create mode 100644 rekono/tools/tools/gobuster.py diff --git a/.secrets.baseline b/.secrets.baseline index d557a43eb..51325c476 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -192,7 +192,7 @@ "filename": "rekono/findings/enums.py", "hashed_secret": "8be3c943b1609fffbfc51aad666d0a04adf83c9d", "is_verified": false, - "line_number": 24, + "line_number": 25, "is_secret": false } ], @@ -849,5 +849,5 @@ } ] }, - "generated_at": "2022-12-31T14:34:49Z" + "generated_at": "2023-01-03T20:35:58Z" } diff --git a/rekono/findings/enums.py b/rekono/findings/enums.py index 7aea5b892..15d392f37 100644 --- a/rekono/findings/enums.py +++ b/rekono/findings/enums.py @@ -16,6 +16,7 @@ class DataType(models.TextChoices): IP = 'IP' DOMAIN = 'Domain' + VHOST = 'VHOST' URL = 'Url' EMAIL = 'Email' LINK = 'Link' diff --git a/rekono/testing/data/reports/gobuster/dir.txt b/rekono/testing/data/reports/gobuster/dir.txt new file mode 100644 index 000000000..e781e17f7 --- /dev/null +++ b/rekono/testing/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/rekono/testing/data/reports/gobuster/dns.txt b/rekono/testing/data/reports/gobuster/dns.txt new file mode 100644 index 000000000..358d354c0 --- /dev/null +++ b/rekono/testing/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/rekono/testing/data/reports/gobuster/vhost.txt b/rekono/testing/data/reports/gobuster/vhost.txt new file mode 100644 index 000000000..d63c2dac6 --- /dev/null +++ b/rekono/testing/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/rekono/testing/tools/test_gobuster.py b/rekono/testing/tools/test_gobuster.py new file mode 100644 index 000000000..aa509b341 --- /dev/null +++ b/rekono/testing/tools/test_gobuster.py @@ -0,0 +1,59 @@ +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: + 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: + 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: + expected = [ + { + 'model': OSINT, + 'data': 'enquetes.example.com', + 'data_type': DataType.VHOST, + 'source': 'Enumeration' + } + ] + super().check_tool_file_parser('vhost.txt', expected) diff --git a/rekono/tools/fixtures/1_tools.json b/rekono/tools/fixtures/1_tools.json index 5014a4529..8b15435a5 100644 --- a/rekono/tools/fixtures/1_tools.json +++ b/rekono/tools/fixtures/1_tools.json @@ -245,5 +245,18 @@ "reference": "https://github.com/fullhunt/spring4shell-scan", "icon": "https://dkh9ehwkisc4.cloudfront.net/static/images/fullhunt.png" } + }, + { + "model": "tools.tool", + "pk": 20, + "fields": { + "name": "Gobuster", + "command": "gobuster", + "output_format": "txt", + "defectdojo_scan_type": null, + "stage": 4, + "reference": "https://github.com/OJ/gobuster", + "icon": null + } } ] \ No newline at end of file diff --git a/rekono/tools/fixtures/2_intensities.json b/rekono/tools/fixtures/2_intensities.json index cd41a9f38..c05a1ed41 100644 --- a/rekono/tools/fixtures/2_intensities.json +++ b/rekono/tools/fixtures/2_intensities.json @@ -268,5 +268,50 @@ "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/rekono/tools/fixtures/3_configurations.json b/rekono/tools/fixtures/3_configurations.json index a4ef13c69..a23cd8e6d 100644 --- a/rekono/tools/fixtures/3_configurations.json +++ b/rekono/tools/fixtures/3_configurations.json @@ -448,5 +448,35 @@ "arguments": "{script} {url} --waf-bypass --test-CVE-2022-22963", "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}", + "default": true + } + }, + { + "model": "tools.configuration", + "pk": 47, + "fields": { + "tool": 20, + "name": "VHOST enumeration", + "arguments": "vhost {target_url} {subdomain_wordlist} {basic_auth} {token_auth} {cookie} {intensity} --append-domain --no-tls-validation --no-color --no-progress --quiet --output {output}", + "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}", + "default": false + } } ] \ No newline at end of file diff --git a/rekono/tools/fixtures/4_arguments.json b/rekono/tools/fixtures/4_arguments.json index bd500a8f6..2b77d46c7 100644 --- a/rekono/tools/fixtures/4_arguments.json +++ b/rekono/tools/fixtures/4_arguments.json @@ -361,5 +361,93 @@ "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": 18, + "name": "cookie", + "argument": "--cookie '{cookie_name}={secret}'", + "required": false, + "multiple": false + } } ] \ No newline at end of file diff --git a/rekono/tools/fixtures/5_inputs.json b/rekono/tools/fixtures/5_inputs.json index a731b4234..190361639 100644 --- a/rekono/tools/fixtures/5_inputs.json +++ b/rekono/tools/fixtures/5_inputs.json @@ -468,5 +468,85 @@ "filter": null, "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": 2, + "filter": null, + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 51, + "fields": { + "argument": 37, + "type": 9, + "filter": "subdomain", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 52, + "fields": { + "argument": 38, + "type": 9, + "filter": "endpoint", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 53, + "fields": { + "argument": 39, + "type": 10, + "filter": "basic", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 54, + "fields": { + "argument": 40, + "type": 10, + "filter": "!cookie,basic", + "order": 1 + } + }, + { + "model": "tools.input", + "pk": 55, + "fields": { + "argument": 41, + "type": 10, + "filter": "cookie", + "order": 1 + } } ] \ No newline at end of file diff --git a/rekono/tools/fixtures/6_outputs.json b/rekono/tools/fixtures/6_outputs.json index dfae41f6d..3008002af 100644 --- a/rekono/tools/fixtures/6_outputs.json +++ b/rekono/tools/fixtures/6_outputs.json @@ -742,5 +742,29 @@ "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/rekono/tools/tools/cmseek.py b/rekono/tools/tools/cmseek.py index f5e6c068b..44c1f9bde 100644 --- a/rekono/tools/tools/cmseek.py +++ b/rekono/tools/tools/cmseek.py @@ -11,7 +11,7 @@ from rekono.settings import TOOLS -class CmseekTool(BaseTool): +class Cmseek(BaseTool): '''CMSeeK tool class.''' # CMSeeK directory where output files can be stored diff --git a/rekono/tools/tools/dirsearch.py b/rekono/tools/tools/dirsearch.py index dbb56756c..ff7f5c36d 100644 --- a/rekono/tools/tools/dirsearch.py +++ b/rekono/tools/tools/dirsearch.py @@ -5,7 +5,7 @@ from tools.tools.base_tool import BaseTool -class DirsearchTool(BaseTool): +class Dirsearch(BaseTool): '''Dirsearch tool class.''' # Exit code ignored because Dirsearch report will include findings until error occurs diff --git a/rekono/tools/tools/emailfinder.py b/rekono/tools/tools/emailfinder.py index 4b92b0d91..dffd893f7 100644 --- a/rekono/tools/tools/emailfinder.py +++ b/rekono/tools/tools/emailfinder.py @@ -5,7 +5,7 @@ from tools.tools.base_tool import BaseTool -class EmailfinderTool(BaseTool): +class Emailfinder(BaseTool): '''EmailFinder tool class.''' def parse_plain_output(self, output: str) -> None: diff --git a/rekono/tools/tools/emailharvester.py b/rekono/tools/tools/emailharvester.py index e00bd1d88..17c2c12b2 100644 --- a/rekono/tools/tools/emailharvester.py +++ b/rekono/tools/tools/emailharvester.py @@ -3,7 +3,7 @@ from tools.tools.base_tool import BaseTool -class EmailharvesterTool(BaseTool): +class Emailharvester(BaseTool): '''EmailHarvester tool class.''' def parse_output_file(self) -> None: diff --git a/rekono/tools/tools/gitleaks.py b/rekono/tools/tools/gitleaks.py index 031e03b33..652d5564f 100644 --- a/rekono/tools/tools/gitleaks.py +++ b/rekono/tools/tools/gitleaks.py @@ -13,7 +13,7 @@ from tools.tools.base_tool import BaseTool -class GitleaksTool(BaseTool): +class Gitleaks(BaseTool): '''GitLeaks tool class.''' # Exit code ignored because GitLeaks fails when find secrets diff --git a/rekono/tools/tools/gobuster.py b/rekono/tools/tools/gobuster.py new file mode 100644 index 000000000..b63c7d648 --- /dev/null +++ b/rekono/tools/tools/gobuster.py @@ -0,0 +1,63 @@ +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) + if '--url' not in arguments and '--domain' not in arguments: + raise ToolExecutionException('Tool configuration requires url or domain argument') + if '--wordlist' not in arguments: + 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: + if ' (Status: ' in line and ') [Size: ' in line: + 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, status = line.replace('Found: ', '').split(' Status: ') + if int(status.split(' [')[0].strip()) == 200: + if '://' in vhost: + vhost = vhost.split('://')[1] + self.create_finding( + OSINT, + data=vhost.strip(), + data_type=DataType.VHOST, + source='Enumeration' + ) + elif ' [' in line and ']' in line: + subdomain, addresses = line.replace('Found: ', '').split(' [') + ips = addresses.replace(']', '').split(',') + self.create_finding(OSINT, data=subdomain, data_type=DataType.DOMAIN, source='DNS') + for ip in ips: + self.create_finding(OSINT, data=ip, data_type=DataType.IP, source='DNS') diff --git a/rekono/tools/tools/joomscan.py b/rekono/tools/tools/joomscan.py index 5410ecd24..b0fbfb9df 100644 --- a/rekono/tools/tools/joomscan.py +++ b/rekono/tools/tools/joomscan.py @@ -3,7 +3,7 @@ from tools.tools.base_tool import BaseTool -class JoomscanTool(BaseTool): +class Joomscan(BaseTool): '''JoomScan tool class.''' def parse_plain_output(self, output: str) -> None: diff --git a/rekono/tools/tools/log4j_scan.py b/rekono/tools/tools/log4j_scan.py index ed1bd9349..80b82baca 100644 --- a/rekono/tools/tools/log4j_scan.py +++ b/rekono/tools/tools/log4j_scan.py @@ -6,7 +6,7 @@ from tools.tools.base_tool import BaseTool -class Log4jscanTool(BaseTool): +class Log4jscan(BaseTool): '''Log4j Scan tool class.''' run_directory = TOOLS['log4j-scan']['directory'] diff --git a/rekono/tools/tools/metasploit.py b/rekono/tools/tools/metasploit.py index 56efe7d09..e35457d77 100644 --- a/rekono/tools/tools/metasploit.py +++ b/rekono/tools/tools/metasploit.py @@ -3,7 +3,7 @@ from tools.tools.base_tool import BaseTool -class MetasploitTool(BaseTool): +class Metasploit(BaseTool): '''Metasploit tool class.''' def parse_plain_output(self, output: str) -> None: diff --git a/rekono/tools/tools/nikto.py b/rekono/tools/tools/nikto.py index aed1c423e..2b15cc04a 100644 --- a/rekono/tools/tools/nikto.py +++ b/rekono/tools/tools/nikto.py @@ -5,7 +5,7 @@ from tools.tools.base_tool import BaseTool -class NiktoTool(BaseTool): +class Nikto(BaseTool): '''Nikto tool class.''' # Exit code ignored because Nikto report will include findings until error occurs diff --git a/rekono/tools/tools/nmap.py b/rekono/tools/tools/nmap.py index 9350835ea..d17948515 100644 --- a/rekono/tools/tools/nmap.py +++ b/rekono/tools/tools/nmap.py @@ -10,7 +10,7 @@ from tools.tools.base_tool import BaseTool -class NmapTool(BaseTool): +class Nmap(BaseTool): '''Nmap tool class.''' def get_smb_technology(self, technologies: Dict[str, Technology]) -> Union[Technology, None]: diff --git a/rekono/tools/tools/nuclei.py b/rekono/tools/tools/nuclei.py index bf94ed489..9dcdba709 100644 --- a/rekono/tools/tools/nuclei.py +++ b/rekono/tools/tools/nuclei.py @@ -7,7 +7,7 @@ from tools.tools.base_tool import BaseTool -class NucleiTool(BaseTool): +class Nuclei(BaseTool): '''Nuclei tool class.''' def parse_output_file(self) -> None: diff --git a/rekono/tools/tools/searchsploit.py b/rekono/tools/tools/searchsploit.py index b647eadec..f16f234ab 100644 --- a/rekono/tools/tools/searchsploit.py +++ b/rekono/tools/tools/searchsploit.py @@ -4,7 +4,7 @@ from tools.tools.base_tool import BaseTool -class SearchsploitTool(BaseTool): +class Searchsploit(BaseTool): '''SearchSploit tool class.''' def parse_output_file(self) -> None: diff --git a/rekono/tools/tools/smbmap.py b/rekono/tools/tools/smbmap.py index 74d96d8a7..9826e1723 100644 --- a/rekono/tools/tools/smbmap.py +++ b/rekono/tools/tools/smbmap.py @@ -3,7 +3,7 @@ from tools.tools.base_tool import BaseTool -class SmbmapTool(BaseTool): +class Smbmap(BaseTool): '''Smbmap tool class.''' def parse_plain_output(self, output: str) -> None: diff --git a/rekono/tools/tools/spring4shell_scan.py b/rekono/tools/tools/spring4shell_scan.py index fd5561999..718aac035 100644 --- a/rekono/tools/tools/spring4shell_scan.py +++ b/rekono/tools/tools/spring4shell_scan.py @@ -6,7 +6,7 @@ from tools.tools.base_tool import BaseTool -class Spring4shellscanTool(BaseTool): +class Spring4shellscan(BaseTool): '''Spring4Shell Scan tool class.''' # Indicate the script path to execute diff --git a/rekono/tools/tools/ssh_audit.py b/rekono/tools/tools/ssh_audit.py index d008996f3..c234706e2 100644 --- a/rekono/tools/tools/ssh_audit.py +++ b/rekono/tools/tools/ssh_audit.py @@ -5,7 +5,7 @@ from tools.tools.base_tool import BaseTool -class SshauditTool(BaseTool): +class Sshaudit(BaseTool): '''SSH Audit tool class.''' # Exit code ignored because SSH Audit fails when find vulnerabilities diff --git a/rekono/tools/tools/sslscan.py b/rekono/tools/tools/sslscan.py index debcc9875..a0586124c 100644 --- a/rekono/tools/tools/sslscan.py +++ b/rekono/tools/tools/sslscan.py @@ -7,7 +7,7 @@ from tools.tools.base_tool import BaseTool -class SslscanTool(BaseTool): +class Sslscan(BaseTool): '''Sslscan tool class.''' def get_technology(self, technologies: List[Technology], sslversion: str) -> Union[Technology, None]: diff --git a/rekono/tools/tools/sslyze.py b/rekono/tools/tools/sslyze.py index 410b4ca42..f506cf8d9 100644 --- a/rekono/tools/tools/sslyze.py +++ b/rekono/tools/tools/sslyze.py @@ -7,7 +7,7 @@ from tools.tools.base_tool import BaseTool -class SslyzeTool(BaseTool): +class Sslyze(BaseTool): '''SSLyze tool class.''' # Exit code ignored because SSLyze can "fail" when find vulnerabilities diff --git a/rekono/tools/tools/theharvester.py b/rekono/tools/tools/theharvester.py index bd3e66e5f..4462128ea 100644 --- a/rekono/tools/tools/theharvester.py +++ b/rekono/tools/tools/theharvester.py @@ -2,17 +2,18 @@ from findings.enums import DataType from findings.models import OSINT + from tools.tools.base_tool import BaseTool -class TheharvesterTool(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.DOMAIN), + ('vhosts', DataType.VHOST), ('urls', DataType.URL), ('trello_urls', DataType.URL), ('interesting_urls', DataType.URL), diff --git a/rekono/tools/tools/zap.py b/rekono/tools/tools/zap.py index 3fa98379d..75bf347e1 100644 --- a/rekono/tools/tools/zap.py +++ b/rekono/tools/tools/zap.py @@ -6,7 +6,7 @@ from tools.tools.base_tool import BaseTool -class ZapTool(BaseTool): +class Zap(BaseTool): '''OWASP ZAP tool class.''' # Mapping between OWASP ZAP severity values and Rekono severity values diff --git a/rekono/tools/utils.py b/rekono/tools/utils.py index 9fb6b3d7f..0b4cb69ce 100644 --- a/rekono/tools/utils.py +++ b/rekono/tools/utils.py @@ -14,7 +14,7 @@ def get_tool_class_by_name(name: str) -> Any: try: tools_module = importlib.import_module(f'tools.tools.{name.lower().replace(" ", "_")}') # Import tool module # Get tool class - tool_class = getattr(tools_module, f'{name[0].upper()}{name[1:].lower().replace(" ", "")}Tool') + 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 From f7352d99ffb69196dc3d2ae7008555a46b144b5d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 3 Jan 2023 21:44:52 +0100 Subject: [PATCH 06/27] Gobuster installation in Docker environment --- README.md | 1 + docker/kali/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index afaca911a..4a809060b 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Why not automate this process and focus on find vulnerabilities using your skill - [SSH Audit](https://github.com/jtesta/ssh-audit) - [SMBMap](https://github.com/ShawnDEvans/smbmap) - [Dirsearch](https://github.com/maurosoria/dirsearch) +- [Gobuster](https://github.com/OJ/gobuster) - [GitLeaks](https://github.com/zricethezav/gitleaks) & [GitDumper](https://github.com/internetwache/GitTools/tree/master/Dumper) - [Log4j Scan](https://github.com/fullhunt/log4j-scan) - [Spring4Shell Scan](https://github.com/fullhunt/spring4shell-scan) diff --git a/docker/kali/Dockerfile b/docker/kali/Dockerfile index 1bc4cb76b..3d8ffcaec 100644 --- a/docker/kali/Dockerfile +++ b/docker/kali/Dockerfile @@ -16,7 +16,7 @@ RUN ln -s /usr/bin/python3 /usr/bin/python RUN pip install --upgrade pip RUN pip install -r /code/requirements.txt -RUN apt install nmap dirsearch theharvester nikto sslscan sslyze cmseek zaproxy exploitdb metasploit-framework emailharvester joomscan gitleaks smbmap nuclei -y +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 From c444bbbb7f8ebd7590331f1c5b7b1692cb03b06e Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 3 Jan 2023 21:48:02 +0100 Subject: [PATCH 07/27] Fix tool id in fixtures --- rekono/tools/fixtures/4_arguments.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rekono/tools/fixtures/4_arguments.json b/rekono/tools/fixtures/4_arguments.json index 2b77d46c7..279bb7e50 100644 --- a/rekono/tools/fixtures/4_arguments.json +++ b/rekono/tools/fixtures/4_arguments.json @@ -443,7 +443,7 @@ "model": "tools.argument", "pk": 41, "fields": { - "tool": 18, + "tool": 20, "name": "cookie", "argument": "--cookie '{cookie_name}={secret}'", "required": false, From ddb282eb7247f67cf46a99656c61d3c5daa5ae53 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 3 Jan 2023 21:53:58 +0100 Subject: [PATCH 08/27] Fix error in Gobuster parser --- rekono/tools/tools/gobuster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rekono/tools/tools/gobuster.py b/rekono/tools/tools/gobuster.py index b63c7d648..72cd14f3a 100644 --- a/rekono/tools/tools/gobuster.py +++ b/rekono/tools/tools/gobuster.py @@ -58,6 +58,6 @@ def parse_output_file(self) -> None: elif ' [' in line and ']' in line: subdomain, addresses = line.replace('Found: ', '').split(' [') ips = addresses.replace(']', '').split(',') - self.create_finding(OSINT, data=subdomain, data_type=DataType.DOMAIN, source='DNS') + self.create_finding(OSINT, data=subdomain.strip(), data_type=DataType.DOMAIN, source='DNS') for ip in ips: - self.create_finding(OSINT, data=ip, data_type=DataType.IP, source='DNS') + self.create_finding(OSINT, data=ip.strip(), data_type=DataType.IP, source='DNS') From e1496d38001fd6af6322e3ca2a8ef9f0ca92bd06 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Tue, 3 Jan 2023 22:05:47 +0100 Subject: [PATCH 09/27] Clean code: code comments for documentation --- rekono/testing/tools/test_gobuster.py | 3 +++ rekono/tools/tools/gobuster.py | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/rekono/testing/tools/test_gobuster.py b/rekono/testing/tools/test_gobuster.py index aa509b341..3bf4a80ec 100644 --- a/rekono/testing/tools/test_gobuster.py +++ b/rekono/testing/tools/test_gobuster.py @@ -9,6 +9,7 @@ class GobusterParserTest(ToolParserTest): 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}, @@ -27,6 +28,7 @@ def test_dir(self) -> None: 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, @@ -48,6 +50,7 @@ def test_dns(self) -> None: super().check_tool_file_parser('dns.txt', expected) def test_vhost(self) -> None: + '''Test to parse vhost report with VHOSTs.''' expected = [ { 'model': OSINT, diff --git a/rekono/tools/tools/gobuster.py b/rekono/tools/tools/gobuster.py index 72cd14f3a..d7b6e9961 100644 --- a/rekono/tools/tools/gobuster.py +++ b/rekono/tools/tools/gobuster.py @@ -24,10 +24,10 @@ def get_arguments(self, targets: List[BaseInput], previous_findings: List[Findin Returns: List[str]: List of tool arguments to use in the tool execution ''' - arguments = super().get_arguments(targets, previous_findings) - if '--url' not in arguments and '--domain' not in arguments: + 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: + if '--wordlist' not in arguments: # Wordlist is required raise ToolExecutionException('Tool configuration requires wordlist argument') return arguments @@ -35,8 +35,8 @@ 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: - if ' (Status: ' in line and ') [Size: ' in line: + 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, @@ -44,19 +44,19 @@ def parse_output_file(self) -> None: status=int(aux[1].split(')')[0].strip()), type=PathType.ENDPOINT ) - elif ' Status: ' in line and ' [Size: ' in line: + elif ' Status: ' in line and ' [Size: ' in line: # VHOST format vhost, status = line.replace('Found: ', '').split(' Status: ') - if int(status.split(' [')[0].strip()) == 200: + if status.split(' [')[0].strip().startswith('2'): # Create only VHOST with status 2XX if '://' in vhost: - vhost = vhost.split('://')[1] + 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, addresses = line.replace('Found: ', '').split(' [') + 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: From e538829e75b15e7a1907475452d9b7564f7d9a3f Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 4 Jan 2023 23:39:55 +0100 Subject: [PATCH 10/27] Fix Gobuster inputs to scan ports --- .pre-commit-config.yaml | 2 +- .secrets.baseline | 4 ++-- rekono/tools/fixtures/5_inputs.json | 22 ++++++++++++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6629a1fd7..1210e9ce5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: require_serial: true verbose: true - repo: https://github.com/Yelp/detect-secrets - rev: v1.3.0 + rev: v1.4.0 hooks: - id: detect-secrets args: ["--baseline", ".secrets.baseline"] diff --git a/.secrets.baseline b/.secrets.baseline index 51325c476..8c8084365 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1,5 +1,5 @@ { - "version": "1.3.0", + "version": "1.4.0", "plugins_used": [ { "name": "ArtifactoryDetector" @@ -849,5 +849,5 @@ } ] }, - "generated_at": "2023-01-03T20:35:58Z" + "generated_at": "2023-01-04T22:38:13Z" } diff --git a/rekono/tools/fixtures/5_inputs.json b/rekono/tools/fixtures/5_inputs.json index 190361639..fc4ac9204 100644 --- a/rekono/tools/fixtures/5_inputs.json +++ b/rekono/tools/fixtures/5_inputs.json @@ -494,14 +494,24 @@ "pk": 50, "fields": { "argument": 36, - "type": 2, - "filter": null, + "type": 3, + "filter": "http", "order": 1 } }, { "model": "tools.input", "pk": 51, + "fields": { + "argument": 36, + "type": 2, + "filter": null, + "order": 2 + } + }, + { + "model": "tools.input", + "pk": 52, "fields": { "argument": 37, "type": 9, @@ -511,7 +521,7 @@ }, { "model": "tools.input", - "pk": 52, + "pk": 53, "fields": { "argument": 38, "type": 9, @@ -521,7 +531,7 @@ }, { "model": "tools.input", - "pk": 53, + "pk": 54, "fields": { "argument": 39, "type": 10, @@ -531,7 +541,7 @@ }, { "model": "tools.input", - "pk": 54, + "pk": 55, "fields": { "argument": 40, "type": 10, @@ -541,7 +551,7 @@ }, { "model": "tools.input", - "pk": 55, + "pk": 56, "fields": { "argument": 41, "type": 10, From 946550cea3690ee767e59101db724c7e5808b4f2 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Wed, 4 Jan 2023 23:46:11 +0100 Subject: [PATCH 11/27] Include Gobuster to default processes --- rekono/processes/fixtures/2_steps.json | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/rekono/processes/fixtures/2_steps.json b/rekono/processes/fixtures/2_steps.json index 697eabcdd..df455c11c 100644 --- a/rekono/processes/fixtures/2_steps.json +++ b/rekono/processes/fixtures/2_steps.json @@ -648,5 +648,75 @@ "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 From 79f2d40d4a1ac0d92e3ac1d871e7dfa6e114c09d Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 5 Jan 2023 00:30:54 +0100 Subject: [PATCH 12/27] Test branch protection --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4a809060b..44e91918e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ ## Features + - Combine hacking tools to create pentesting `processes` - Execute pentesting `processes` - Execute pentesting `tools` From 8b2c9cd7c06a1387d112b28e9717f317d3bbd4bb Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 5 Jan 2023 00:31:39 +0100 Subject: [PATCH 13/27] Test branch protection --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 44e91918e..4a809060b 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ ## Features - - Combine hacking tools to create pentesting `processes` - Execute pentesting `processes` - Execute pentesting `tools` From b892e8a964a090f258d38c8d58407fb3967290c5 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 5 Jan 2023 00:32:48 +0100 Subject: [PATCH 14/27] Test branch protection --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4a809060b..44e91918e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ ## Features + - Combine hacking tools to create pentesting `processes` - Execute pentesting `processes` - Execute pentesting `tools` From 4d528e3016a51127cd1b1a739485147d97f23121 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 5 Jan 2023 00:33:42 +0100 Subject: [PATCH 15/27] Test branch protection --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 44e91918e..4a809060b 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ ## Features - - Combine hacking tools to create pentesting `processes` - Execute pentesting `processes` - Execute pentesting `tools` From 1a3938a0277d0125ed0d92d328e73b87d6fb58e8 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 5 Jan 2023 00:36:00 +0100 Subject: [PATCH 16/27] Test branch protection --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4a809060b..44e91918e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ ## Features + - Combine hacking tools to create pentesting `processes` - Execute pentesting `processes` - Execute pentesting `tools` From f60536ea1b88675c36aa7beffb5852864181d47c Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 5 Jan 2023 15:52:35 +0100 Subject: [PATCH 17/27] Distinct filter for target and hosts --- README.md | 1 - rekono/findings/models.py | 8 ++++-- rekono/targets/models.py | 6 ++++- rekono/tools/fixtures/5_inputs.json | 38 ++++++++++++++--------------- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 44e91918e..4a809060b 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ ## Features - - Combine hacking tools to create pentesting `processes` - Execute pentesting `processes` - Execute pentesting `tools` diff --git a/rekono/findings/models.py b/rekono/findings/models.py index 278401182..c79df9d2f 100644 --- a/rekono/findings/models.py +++ b/rekono/findings/models.py @@ -165,8 +165,12 @@ def filter(self, input: Input) -> bool: if not input.filter: return True try: - # Filter by address type - return cast(models.TextChoices, TargetType)[input.filter] == get_target_type(self.address) + 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 diff --git a/rekono/targets/models.py b/rekono/targets/models.py index f983473aa..5544d1fc2 100644 --- a/rekono/targets/models.py +++ b/rekono/targets/models.py @@ -47,7 +47,11 @@ def filter(self, input: Input) -> bool: if not input.filter: return True try: - return cast(models.TextChoices, TargetType)[input.filter] == self.type + 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 diff --git a/rekono/tools/fixtures/5_inputs.json b/rekono/tools/fixtures/5_inputs.json index fc4ac9204..ade902a85 100644 --- a/rekono/tools/fixtures/5_inputs.json +++ b/rekono/tools/fixtures/5_inputs.json @@ -45,7 +45,7 @@ "fields": { "argument": 3, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -65,7 +65,7 @@ "fields": { "argument": 5, "type": 2, - "filter": "DOMAIN", + "filter": "domain", "order": 1 } }, @@ -85,7 +85,7 @@ "fields": { "argument": 6, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -105,7 +105,7 @@ "fields": { "argument": 7, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -125,7 +125,7 @@ "fields": { "argument": 8, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -145,7 +145,7 @@ "fields": { "argument": 9, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -165,7 +165,7 @@ "fields": { "argument": 10, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -205,7 +205,7 @@ "fields": { "argument": 13, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -215,7 +215,7 @@ "fields": { "argument": 14, "type": 2, - "filter": "DOMAIN", + "filter": "domain", "order": 1 } }, @@ -225,7 +225,7 @@ "fields": { "argument": 15, "type": 2, - "filter": "DOMAIN", + "filter": "domain", "order": 1 } }, @@ -245,7 +245,7 @@ "fields": { "argument": 16, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -265,7 +265,7 @@ "fields": { "argument": 17, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -285,7 +285,7 @@ "fields": { "argument": 18, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -315,7 +315,7 @@ "fields": { "argument": 20, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -425,7 +425,7 @@ "fields": { "argument": 30, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -465,7 +465,7 @@ "fields": { "argument": 33, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, @@ -475,7 +475,7 @@ "fields": { "argument": 34, "type": 2, - "filter": "DOMAIN", + "filter": "domain", "order": 1 } }, @@ -485,7 +485,7 @@ "fields": { "argument": 35, "type": 2, - "filter": "DOMAIN", + "filter": "domain", "order": 1 } }, @@ -505,7 +505,7 @@ "fields": { "argument": 36, "type": 2, - "filter": null, + "filter": "!ip_range,network", "order": 2 } }, From a5ecd9a750bcb2e0ac67a6ce0e316b82089f481b Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Thu, 5 Jan 2023 16:05:49 +0100 Subject: [PATCH 18/27] Show unique input types for each tool --- rekono/frontend/src/components/Tools.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rekono/frontend/src/components/Tools.vue b/rekono/frontend/src/components/Tools.vue index 3c4c4e2f5..284d1d28f 100644 --- a/rekono/frontend/src/components/Tools.vue +++ b/rekono/frontend/src/components/Tools.vue @@ -16,7 +16,7 @@ + - + + + diff --git a/rekono/telegram_bot/conversations/ask.py b/rekono/telegram_bot/conversations/ask.py index cd3cc4188..2842f46e8 100644 --- a/rekono/telegram_bot/conversations/ask.py +++ b/rekono/telegram_bot/conversations/ask.py @@ -1,6 +1,7 @@ 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 @@ -216,7 +217,7 @@ def ask_for_tool(update: Update, context: CallbackContext, chat: TelegramChat) - 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, 3) + send_options(update, chat, ASK_FOR_TOOL, keyboard, 2) return SELECT_TOOL # Go to selected tool management @@ -254,20 +255,32 @@ def ask_for_wordlist(update: Update, context: CallbackContext, chat: TelegramCha ''' wordlists = Wordlist.objects.all() # Get all wordlists # Create keyboard buttons with the wordlists data - keyboard = [InlineKeyboardButton(w.name, callback_data=w.id) for w in wordlists] + 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 context.chat_data is not None and context.chat_data.get(TOOL): # Filter inputs by tool + 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 + ): + print('TOOL') check_if_wordlist_is_required = {'argument__tool': context.chat_data[TOOL]} - elif context.chat_data is not None and context.chat_data.get(PROCESS): # Filter inputs by process + 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() + ): + print('PROCESS') check_if_wordlist_is_required = {'argument__tool__in': context.chat_data[PROCESS].steps.all().values('tool')} + print(check_if_wordlist_is_required) if check_if_wordlist_is_required: check_if_wordlist_is_required.update({ # Base arguments to check if required 'argument__required': True, - 'type__name': 'Wordlist' + 'type__name': InputTypeNames.WORDLIST }) if not Input.objects.filter(**check_if_wordlist_is_required).exists(): # Check if wordlist is required - keyboard.append(InlineKeyboardButton('Default wordlists', callback_data='Default wordlists')) - send_options(update, chat, ASK_FOR_WORDLIST, keyboard, 2) + 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 diff --git a/rekono/telegram_bot/conversations/selection.py b/rekono/telegram_bot/conversations/selection.py index 11c1dd4ad..29073e401 100644 --- a/rekono/telegram_bot/conversations/selection.py +++ b/rekono/telegram_bot/conversations/selection.py @@ -226,7 +226,7 @@ def select_wordlist(update: Update, context: CallbackContext) -> int: if ( chat and context.chat_data is not None and update.callback_query and update.callback_query.data and - update.callback_query.data != 'Default wordlists' + 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 From 73b1a904ffad4ef0b8a2cf787f014ea22e97cf02 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 6 Jan 2023 17:21:36 +0100 Subject: [PATCH 24/27] Improve resources filter by likes in web UI --- rekono/frontend/src/common/TableHeader.vue | 3 ++- rekono/frontend/src/components/Processes.vue | 2 +- rekono/frontend/src/components/Tools.vue | 2 +- rekono/frontend/src/components/Wordlists.vue | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rekono/frontend/src/common/TableHeader.vue b/rekono/frontend/src/common/TableHeader.vue index f7cf8229e..7bd041e99 100644 --- a/rekono/frontend/src/common/TableHeader.vue +++ b/rekono/frontend/src/common/TableHeader.vue @@ -31,12 +31,13 @@ - + + diff --git a/rekono/frontend/src/components/Processes.vue b/rekono/frontend/src/components/Processes.vue index bb82ad728..7909f5fcc 100644 --- a/rekono/frontend/src/components/Processes.vue +++ b/rekono/frontend/src/components/Processes.vue @@ -134,7 +134,7 @@ export default { { name: 'Tags', filterField: 'tags__name__in', type: 'tags' }, { name: 'Stage', values: this.stages, valueField: 'id', textField: 'value', filterField: 'steps__configuration__stage' }, { name: 'Creator', filterField: 'creator__username__icontains', type: 'text' }, - { name: 'Favourities', values: [{ value: true, text: 'True' }, { value: false, text: 'False' }], valueField: 'value', textField: 'text', filterField: 'liked' } + { name: 'Favourities', type: 'checkbox', filterField: 'liked' } ] } }, diff --git a/rekono/frontend/src/components/Tools.vue b/rekono/frontend/src/components/Tools.vue index 17859f1d7..7c3b56c8e 100644 --- a/rekono/frontend/src/components/Tools.vue +++ b/rekono/frontend/src/components/Tools.vue @@ -113,7 +113,7 @@ export default { { name: 'Stage', values: this.stages, valueField: 'id', textField: 'value', filterField: 'configurations__stage' }, { name: 'Input', values: this.inputTypes, valueField: 'value', textField: 'value', filterField: 'arguments__inputs__type__name' }, { name: 'Output', values: this.inputTypes, valueField: 'value', textField: 'value', filterField: 'configurations__outputs__type__name' }, - { name: 'Favourities', values: [{ value: true, text: 'True' }, { value: false, text: 'False' }], valueField: 'value', textField: 'text', filterField: 'liked' } + { name: 'Favourities', type: 'checkbox', filterField: 'liked' } ] } }, diff --git a/rekono/frontend/src/components/Wordlists.vue b/rekono/frontend/src/components/Wordlists.vue index 677885bce..d50de9ea3 100644 --- a/rekono/frontend/src/components/Wordlists.vue +++ b/rekono/frontend/src/components/Wordlists.vue @@ -70,7 +70,7 @@ export default { { name: 'Type', values: ['Endpoint', 'Subdomain'], valueField: 'value', textField: 'value', filterField: 'type' }, { name: 'Max Size', filterField: 'size__lte', type: 'number' }, { name: 'Creator', filterField: 'creator__username__icontains', type: 'text' }, - { name: 'Favourities', values: [{ value: true, text: 'True' }, { value: false, text: 'False' }], valueField: 'value', textField: 'text', filterField: 'liked' } + { name: 'Favourities', type: 'checkbox', filterField: 'liked' } ] } }, From a224627feceec7d51703c9fd7e23fd4586ea3b2c Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 6 Jan 2023 17:44:08 +0100 Subject: [PATCH 25/27] Fix filters for ports and paths --- rekono/findings/filters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rekono/findings/filters.py b/rekono/findings/filters.py index e961247f8..3e378db5c 100644 --- a/rekono/findings/filters.py +++ b/rekono/findings/filters.py @@ -162,7 +162,9 @@ 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')) + o = OrderingFilter( + fields=FINDING_ORDERING + (('host__os_type', 'os_type'), 'host', 'port', 'protocol', 'service', 'status') + ) class Meta: '''FilterSet metadata.''' @@ -184,7 +186,7 @@ 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')) + o = OrderingFilter(fields=FINDING_ORDERING + (('port__host', 'host'), 'port', 'path', 'status', 'type')) class Meta: '''FilterSet metadata.''' From 31094d3f12f2089f449e838c2dca74511e528712 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 6 Jan 2023 17:45:10 +0100 Subject: [PATCH 26/27] Fix loading gif for findings to prevent API errors --- .../src/components/findings/Finding.vue | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/rekono/frontend/src/components/findings/Finding.vue b/rekono/frontend/src/components/findings/Finding.vue index 96d5a351d..3aab33c19 100644 --- a/rekono/frontend/src/components/findings/Finding.vue +++ b/rekono/frontend/src/components/findings/Finding.vue @@ -178,15 +178,17 @@ export default { filter = this.getFilter() } filter.severity = this.severities[index] - this.getAllPages('/api/vulnerabilities/', filter).then(results => { - this.findings.push(...results) - index += 1 - if (index < this.severities.length) { - this.fetchVulnerabilities(filter, index) - } else { - this.$emit('end') - } - }) + this.getAllPages('/api/vulnerabilities/', filter) + .then(results => { + this.findings.push(...results) + index += 1 + if (index < this.severities.length) { + this.fetchVulnerabilities(filter, index) + } else { + this.$emit('end') + } + }) + .catch(() => this.$emit('end')) }, fetchData () { if (this.types && this.types.length > 0 && !this.types.includes(this.name.toLowerCase())) { @@ -200,6 +202,7 @@ export default { this.findings = results this.$emit('end') }) + .catch(() => this.$emit('end')) } else { this.fetchVulnerabilities() } From 431d532dc215dc1d41e8708228213f56dedda8ec Mon Sep 17 00:00:00 2001 From: Pablo Santiago Date: Fri, 6 Jan 2023 17:46:08 +0100 Subject: [PATCH 27/27] Add error message for skipped executions --- rekono/tools/tools/base_tool.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/rekono/tools/tools/base_tool.py b/rekono/tools/tools/base_tool.py index bd5c16303..b5168f288 100644 --- a/rekono/tools/tools/base_tool.py +++ b/rekono/tools/tools/base_tool.py @@ -390,11 +390,16 @@ def on_start(self) -> None: self.execution.start = timezone.now() # Set execution start date self.execution.save(update_fields=['start']) - def on_skipped(self) -> None: - '''Perform changes in Execution entity when tool execution is skipped.''' + 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_error = message self.execution.end = timezone.now() # Set execution end date - self.execution.save(update_fields=['status', 'end']) + self.execution.save(update_fields=['status', 'end', 'output_error']) def on_running(self) -> None: '''Perform changes in Execution entity when command execution starts.''' @@ -440,9 +445,9 @@ def run(self, targets: List[BaseInput], previous_findings: List[Finding]) -> Non self.on_start() # Start execution try: self.check_installation() # Check tool installation - except ToolExecutionException: # Tool installation not found + 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() # Skip execution + self.on_skipped(str(ex)) # Skip execution return try: # Get arguments to include in command @@ -450,7 +455,7 @@ def run(self, targets: List[BaseInput], previous_findings: List[Finding]) -> Non except ToolExecutionException as ex: logger.error(f'[Tool] {str(ex)}') # Targets and findings aren't enough to build the command - self.on_skipped() # Skip execution + self.on_skipped(str(ex)) # Skip execution return self.prepare_environment() # Prepare environment self.on_running() # Run execution