New upstream version 1.4.1
Sophie Brun
3 years ago
13 | 13 | when: never |
14 | 14 | - when: always |
15 | 15 | |
16 | .install_faraday_venv: &install_faraday_venv | |
17 | - pip3 install virtualenv | |
18 | - virtualenv -p python3 faraday_venv | |
19 | - source faraday_venv/bin/activate | |
20 | - pip3 install pytest pytest-xdist pytest-cov | |
21 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/faradaysec/faraday.git | |
22 | - cd faraday | |
23 | - pip3 install $PIP_FLAGS . | |
24 | - pip uninstall faraday-plugins -y # we need to install fardaysec for marshmallow schemas, we remove plugins from pypi | |
25 | - cd .. | |
26 | ||
27 | .clone_reports: &clone_reports | |
28 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/faradaysec/support/report-collection.git | |
29 | - cd report-collection | |
30 | - (git branch -a | grep $CI_COMMIT_BRANCH) && export REPORT_REF=$CI_COMMIT_BRANCH || export REPORT_REF=master | |
31 | - git checkout $REPORT_REF | |
32 | - cd .. | |
33 | ||
34 | ||
16 | 35 | flake8: |
17 | 36 | image: python:3 |
18 | 37 | stage: pre_testing |
26 | 45 | after_script: |
27 | 46 | - wc -l files.processed |
28 | 47 | |
48 | .test_base: | |
49 | stage: testing | |
50 | coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' | |
51 | script: | |
52 | - *clone_reports | |
53 | - *install_faraday_venv | |
54 | - pip3 install $PIP_FLAGS . | |
55 | - pytest tests --capture=sys -v --cov=faraday_plugins --color=yes --disable-warnings $PYTEST_FLAGS | |
56 | ||
29 | 57 | tests: |
30 | image: python:3.7 | |
31 | stage: testing | |
32 | coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' | |
33 | before_script: | |
34 | - pip3 install virtualenv | |
35 | - virtualenv -p python3 faraday_venv | |
36 | - source faraday_venv/bin/activate | |
37 | - pip3 install pytest pytest-xdist pytest-cov | |
38 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/faradaysec/faraday.git | |
39 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/faradaysec/support/report-collection.git | |
40 | - cd faraday | |
41 | - python3 setup.py install | |
42 | - pip uninstall faraday-plugins -y # we need to install fardaysec for marshmallow schemas, we remove plugins from pypi | |
43 | - cd .. | |
44 | script: | |
45 | - source faraday_venv/bin/activate | |
46 | - python3 setup.py install | |
47 | - pytest tests --capture=sys -v --cov=faraday_plugins --color=yes --disable-warnings | |
48 | ||
58 | extends: .test_base | |
59 | image: python:3 | |
49 | 60 | |
50 | 61 | test_performance: |
51 | image: python:3.7 | |
52 | stage: post_testing | |
53 | coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' | |
54 | allow_failure: true | |
55 | before_script: | |
56 | - pip3 install virtualenv | |
57 | - virtualenv -p python3 faraday_venv | |
58 | - source faraday_venv/bin/activate | |
59 | - pip3 install pytest pytest-xdist pytest-cov | |
60 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/faradaysec/faraday.git | |
61 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/faradaysec/support/report-collection.git | |
62 | - cd faraday | |
63 | - python3 setup.py install | |
64 | - pip uninstall faraday-plugins -y # we need to install fardaysec for marshmallow schemas, we remove plugins from pypi | |
65 | - cd .. | |
66 | script: | |
67 | - source faraday_venv/bin/activate | |
68 | - python3 setup.py install | |
69 | - pytest tests --capture=sys -v --cov=faraday_plugins --color=yes --disable-warnings --performance | |
70 | rules: | |
71 | - if: '$CI_COMMIT_BRANCH == "develop"' | |
72 | when: on_success | |
62 | extends: .test_base | |
63 | image: python:3 | |
64 | stage: post_testing | |
65 | allow_failure: true | |
66 | variables: | |
67 | PYTEST_FLAGS: --performance | |
68 | rules: | |
69 | - if: '$CI_COMMIT_BRANCH == "dev"' | |
70 | when: on_success | |
73 | 71 | |
74 | 72 | publish_pypi: |
75 | 73 | image: python:3 |
0 | ADD plugin AppSpider |
0 | Add tests to faraday-plugins cli⏎ |
0 | add a default value to plugin_version⏎ |
0 | Add --output-file parameter to faraday-plugins process command⏎ |
0 | Add plugins prowler⏎ |
0 | Add plugins ssl labs⏎ |
0 | Add support for tenable io⏎ |
0 | Sep 2nd, 2020⏎ |
0 | delete old deprecated methods⏎ |
0 | Bug fix: Arachni Plugin 'NoneType' object has no attribute 'find' |
0 | Bug fix: Openvas Plugin - Import xml from OpenVas doesnt work |
0 | Bug fix: QualysWebApp Plugin, error in get info OPERATING_SYSTEM |
0 | Fix Hydra plugin to resolve ip address⏎ |
0 | Fix Nessus mod severity HIGH for Low ⏎ |
0 | Bug Fix: Detect plugins AWS Prowler |
0 | Fix broken xml on nmap plugin |
0 | Add new rdpscan plugin |
0 | UPDATE xml report to appscan⏎ |
0 | Update Readme⏎ |
0 | Fix how ZAP genereate vulns⏎ |
0 | Dec 23rd, 2020 |
0 | Update the fields of the nuclei output used to create a vuln⏎ |
0 | Add new plugin base class, for multi line json⏎ |
0 | New ncrack plugin ⏎ |
0 | New nuclei plugin⏎ |
0 | New sslyze json plugin⏎ |
0 | New WhatWeb plugin⏎ |
0 | Dec 14th, 2020⏎ |
0 | Fix missing ip in some arachni reports⏎ |
0 | Fix change name vuln in Netsparker plugin⏎ |
0 | Fix whois plugin, command whois IP not parse data⏎ |
0 | Change the way we detect json reports when they are lists of dictionaries⏎ |
0 | Dec 15th, 2020⏎ |
0 | Fix nuclei plugin bug when url is None⏎ |
0 | ADD microsoft baseline security analyzer plugin |
0 | ADD nextnet plugin |
0 | ADD openscap plugin |
0 | Feb 26th, 2021 |
0 | FIX old versions of Nessus plugins bugs |
0 | 1.4.1 [Feb 26th, 2021]: | |
1 | --- | |
2 | * ADD microsoft baseline security analyzer plugin | |
3 | * ADD nextnet plugin | |
4 | * ADD openscap plugin | |
5 | * FIX old versions of Nessus plugins bugs | |
6 | ||
7 | 1.4.0 [Dec 23rd, 2020]: | |
8 | --- | |
9 | * Update the fields of the nuclei output used to create a vuln | |
10 | ||
11 | 1.4.0b2 [Dec 15th, 2020]: | |
12 | --- | |
13 | * Fix nuclei plugin bug when url is None | |
14 | ||
15 | 1.4.0b1 [Dec 14th, 2020]: | |
16 | --- | |
17 | * Add new plugin base class, for multi line json | |
18 | * New ncrack plugin | |
19 | * New nuclei plugin | |
20 | * New sslyze json plugin | |
21 | * New WhatWeb plugin | |
22 | * Fix missing ip in some arachni reports | |
23 | * Fix change name vuln in Netsparker plugin | |
24 | * Fix whois plugin, command whois IP not parse data | |
25 | * Change the way we detect json reports when they are lists of dictionaries | |
26 | ||
27 | 1.3.0 [Sep 2nd, 2020]: | |
28 | --- | |
29 | * ADD plugin AppSpider | |
30 | * Add tests to faraday-plugins cli | |
31 | * add a default value to plugin_version | |
32 | * Add --output-file parameter to faraday-plugins process command | |
33 | * Add plugins prowler | |
34 | * Add plugins ssl labs | |
35 | * Add support for tenable io | |
36 | * delete old deprecated methods | |
37 | * Bug fix: Arachni Plugin 'NoneType' object has no attribute 'find' | |
38 | * Bug fix: Openvas Plugin - Import xml from OpenVas doesnt work | |
39 | * Bug fix: QualysWebApp Plugin, error in get info OPERATING_SYSTEM | |
40 | * Fix Hydra plugin to resolve ip address | |
41 | * Fix Nessus mod severity HIGH for Low | |
42 | * Bug Fix: Detect plugins AWS Prowler | |
43 | * Fix broken xml on nmap plugin | |
44 | * Add new rdpscan plugin | |
45 | * UPDATE xml report to appscan | |
46 | * Update Readme | |
47 | * Fix how ZAP genereate vulns | |
48 |
7 | 7 | |
8 | 8 | > List Plugins |
9 | 9 | |
10 | List all plugins and if its compatible with command or/and report | |
11 | ||
12 | Optional params: | |
13 | ||
14 | - -cpf / --custom-plugins-folder PATH: If given will also look for custom plugins on that path | |
15 | ||
10 | 16 | ```shell script |
11 | faraday-plugins show | |
17 | faraday-plugins list-plugins | |
12 | 18 | ``` |
13 | 19 | |
14 | 20 | > Test autodetect plugin from command |
15 | 21 | |
16 | 22 | ```shell script |
17 | 23 | faraday-plugins detect-command "ping -c 4 www.google.com" |
18 | > Faraday Plugin: ping | |
24 | ||
25 | Faraday Plugin: ping | |
19 | 26 | ``` |
20 | 27 | |
21 | > Test command with plugin | |
28 | > Test process command with plugin | |
22 | 29 | |
23 | 30 | Optional params: |
24 | 31 | |
25 | - -dr: Dont run, just show the generated command | |
32 | - --plugin_id PLUGIN_ID: Dont detect the plugin, use this one | |
33 | - -cpf / --custom-plugins-folder PATH: If given will also look for custom plugins on that path | |
34 | - -dr / --dont-run: Dont run, just show the generated command | |
35 | - -o / --output-file PATH: send json outout to file instead of stdout | |
36 | - -sh / --show-output: show the output of the command | |
26 | 37 | |
27 | 38 | ```shell script |
28 | 39 | faraday-plugins process-command "ping -c4 www.google.com" |
29 | Running command: ping -c4 www.google.com | |
30 | ||
31 | PING www.google.com (216.58.222.36): 56 data bytes | |
32 | 64 bytes from 216.58.222.36: icmp_seq=0 ttl=54 time=11.144 ms | |
33 | 64 bytes from 216.58.222.36: icmp_seq=1 ttl=54 time=14.330 ms | |
34 | 64 bytes from 216.58.222.36: icmp_seq=2 ttl=54 time=11.997 ms | |
35 | 64 bytes from 216.58.222.36: icmp_seq=3 ttl=54 time=11.190 ms | |
36 | ||
37 | --- www.google.com ping statistics --- | |
38 | 4 packets transmitted, 4 packets received, 0.0% packet loss | |
39 | round-trip min/avg/max/stddev = 11.144/12.165/14.330/1.295 ms | |
40 | ||
41 | Faraday API json: | |
42 | 40 | { |
43 | 41 | "hosts": [ |
44 | 42 | { |
45 | "ip": "216.58.222.36", | |
43 | "ip": "216.58.202.36", | |
46 | 44 | "os": "unknown", |
47 | 45 | "hostnames": [ |
48 | 46 | "www.google.com" |
49 | 47 | ], |
50 | 48 | "description": "", |
51 | "mac": "00:00:00:00:00:00", | |
49 | "mac": null, | |
52 | 50 | "credentials": [], |
53 | 51 | "services": [], |
54 | "vulnerabilities": [] | |
52 | "vulnerabilities": [], | |
53 | "tags": [] | |
55 | 54 | } |
56 | 55 | ], |
57 | 56 | "command": { |
58 | 57 | "tool": "ping", |
59 | 58 | "command": "ping", |
60 | 59 | "params": "-c4 www.google.com", |
61 | "user": "aenima", | |
60 | "user": "user", | |
62 | 61 | "hostname": "", |
63 | "start_date": "2020-05-05T23:09:39.656132", | |
64 | "duration": 56789, | |
65 | "import_source": "report" | |
62 | "start_date": "2020-06-19T17:02:37.982293", | |
63 | "duration": 39309, | |
64 | "import_source": "shell" | |
66 | 65 | } |
67 | 66 | } |
68 | 67 | ``` |
71 | 70 | |
72 | 71 | ```shell script |
73 | 72 | faraday-plugins detect-report /path/to/report.xml |
73 | ||
74 | Faraday Plugin: Nmap | |
74 | 75 | ``` |
75 | 76 | |
76 | 77 | |
77 | 78 | > Test report with plugin |
78 | 79 | |
80 | Optional params: | |
81 | ||
82 | - --plugin_id PLUGIN_ID: Dont detect the plugin, use this one | |
83 | - -cpf / --custom-plugins-folder PATH: If given will also look for custom plugins on that path | |
84 | ||
79 | 85 | ```shell script |
80 | faraday-plugins process-report /path/to/report.xml | |
86 | faraday-plugins process-report /path/to/nmap_report.xml | |
87 | ||
88 | { | |
89 | "hosts": [ | |
90 | { | |
91 | "ip": "192.168.66.1", | |
92 | "os": "unknown", | |
93 | "hostnames": [], | |
94 | "description": "", | |
95 | "mac": "00:00:00:00:00:00", | |
96 | "credentials": [], | |
97 | "services": [ | |
98 | { | |
99 | "name": "domain", | |
100 | "protocol": "tcp", | |
101 | "port": 53, | |
102 | "status": "open", | |
103 | "version": "", | |
104 | "description": "domain", | |
105 | "credentials": [], | |
106 | "vulnerabilities": [], | |
107 | "tags": [] | |
108 | }, | |
109 | { | |
110 | "name": "netbios-ssn", | |
111 | "protocol": "tcp", | |
112 | "port": 139, | |
113 | "status": "open", | |
114 | "version": "", | |
115 | "description": "netbios-ssn", | |
116 | "credentials": [], | |
117 | "vulnerabilities": [], | |
118 | "tags": [] | |
119 | } | |
120 | ], | |
121 | "vulnerabilities": [], | |
122 | "tags": [] | |
123 | } | |
124 | ], | |
125 | "command": { | |
126 | "tool": "Nmap", | |
127 | "command": "Nmap", | |
128 | "params": "/path/to/nmap_report.xml", | |
129 | "user": "user", | |
130 | "hostname": "", | |
131 | "start_date": "2020-06-19T17:22:11.608134", | |
132 | "duration": 1233, | |
133 | "import_source": "report" | |
134 | } | |
135 | } | |
81 | 136 | ``` |
82 | ||
83 | > Process options: | |
84 | ||
85 | Both process-xxx command have this optional parameters | |
86 | ||
87 | - --plugin_id: If given will use that plugin instead of try to detect it | |
88 | - --summary: If given will generate a summary of the findings instead of the result | |
89 | - -cpf/--custom-plugins-folder: If given will also look for custom plugins if that path | |
90 | ||
91 | NOTE: you can also use -cpf in **show** command to test if your custom plugins load ok | |
92 | 137 | |
93 | 138 | > Plugin Logger |
94 | 139 |
0 | 1.4.1 [Feb 26th, 2021]: | |
1 | --- | |
2 | * ADD microsoft baseline security analyzer plugin | |
3 | * ADD nextnet plugin | |
4 | * ADD openscap plugin | |
5 | * FIX old versions of Nessus plugins bugs | |
6 | ||
7 | 1.4.0 [Dec 23rd, 2020]: | |
8 | --- | |
9 | * Update the fields of the nuclei output used to create a vuln | |
10 | ||
11 | 1.4.0b2 [Dec 15th, 2020]: | |
12 | --- | |
13 | * Fix nuclei plugin bug when url is None | |
14 | ||
15 | 1.4.0b1 [Dec 14th, 2020]: | |
16 | --- | |
17 | * Add new plugin base class, for multi line json | |
18 | * New ncrack plugin | |
19 | * New nuclei plugin | |
20 | * New sslyze json plugin | |
21 | * New WhatWeb plugin | |
22 | * Fix missing ip in some arachni reports | |
23 | * Fix change name vuln in Netsparker plugin | |
24 | * Fix whois plugin, command whois IP not parse data | |
25 | * Change the way we detect json reports when they are lists of dictionaries | |
26 | ||
27 | 1.3.0 [Sep 2nd, 2020]: | |
28 | --- | |
29 | * ADD plugin AppSpider | |
30 | * Add tests to faraday-plugins cli | |
31 | * add a default value to plugin_version | |
32 | * Add --output-file parameter to faraday-plugins process command | |
33 | * Add plugins prowler | |
34 | * Add plugins ssl labs | |
35 | * Add support for tenable io | |
36 | * delete old deprecated methods | |
37 | * Bug fix: Arachni Plugin 'NoneType' object has no attribute 'find' | |
38 | * Bug fix: Openvas Plugin - Import xml from OpenVas doesnt work | |
39 | * Bug fix: QualysWebApp Plugin, error in get info OPERATING_SYSTEM | |
40 | * Fix Hydra plugin to resolve ip address | |
41 | * Fix Nessus mod severity HIGH for Low | |
42 | * Bug Fix: Detect plugins AWS Prowler | |
43 | * Fix broken xml on nmap plugin | |
44 | * Add new rdpscan plugin | |
45 | * UPDATE xml report to appscan | |
46 | * Update Readme | |
47 | * Fix how ZAP genereate vulns | |
48 |
0 | with import <nixpkgs> {}; | |
1 | pkgs.python38Packages.buildPythonPackage rec { | |
2 | name = "env"; | |
3 | ||
4 | env = buildEnv { name = name; paths = buildInputs; }; | |
5 | ||
6 | buildInputs = [ | |
7 | (python38.buildEnv.override { | |
8 | ignoreCollisions = true; | |
9 | extraLibs = with python38Packages; [ | |
10 | requests | |
11 | click | |
12 | simplejson | |
13 | requests | |
14 | lxml | |
15 | html2text | |
16 | beautifulsoup4 | |
17 | pytz | |
18 | python-dateutil | |
19 | colorama | |
20 | ]; | |
21 | }) | |
22 | ]; | |
23 | } |
50 | 50 | @click.option('--plugin_id', type=str) |
51 | 51 | @click.option('-cpf', '--custom-plugins-folder', type=str) |
52 | 52 | @click.option('--summary', is_flag=True) |
53 | def process_report(report_file, plugin_id, custom_plugins_folder, summary): | |
53 | @click.option('-o', '--output-file', type=click.Path(exists=False)) | |
54 | def process_report(report_file, plugin_id, custom_plugins_folder, summary, output_file): | |
54 | 55 | if not os.path.isfile(report_file): |
55 | click.echo(click.style(f"File {report_file} Don't Exists", fg="red")) | |
56 | click.echo(click.style(f"File {report_file} Don't Exists", fg="red"), err=True) | |
56 | 57 | else: |
57 | 58 | plugins_manager = PluginsManager(custom_plugins_folder) |
58 | 59 | analyzer = ReportAnalyzer(plugins_manager) |
59 | 60 | if plugin_id: |
60 | 61 | plugin = plugins_manager.get_plugin(plugin_id) |
61 | 62 | if not plugin: |
62 | click.echo(click.style(f"Invalid Plugin: {plugin_id}", fg="red")) | |
63 | click.echo(click.style(f"Invalid Plugin: {plugin_id}", fg="red"), err=True) | |
63 | 64 | return |
64 | 65 | else: |
65 | 66 | plugin = analyzer.get_plugin(report_file) |
66 | 67 | if not plugin: |
67 | click.echo(click.style(f"Failed to detect report: {report_file}", fg="red")) | |
68 | click.echo(click.style(f"Failed to detect report: {report_file}", fg="red"), err=True) | |
68 | 69 | return |
69 | 70 | plugin.processReport(report_file, getpass.getuser()) |
70 | 71 | if summary: |
71 | click.echo(click.style("\nPlugin Summary: ", fg="cyan")) | |
72 | 72 | click.echo(json.dumps(plugin.get_summary(), indent=4)) |
73 | 73 | else: |
74 | click.echo(click.style("\nFaraday API json: ", fg="cyan")) | |
75 | click.echo(json.dumps(plugin.get_data(), indent=4)) | |
74 | if output_file: | |
75 | with open(output_file, "w") as f: | |
76 | json.dump(plugin.get_data(), f) | |
77 | else: | |
78 | click.echo(json.dumps(plugin.get_data(), indent=4)) | |
76 | 79 | |
77 | 80 | |
78 | 81 | @cli.command() |
81 | 84 | @click.option('-cpf', '--custom-plugins-folder', type=str) |
82 | 85 | @click.option('-dr', '--dont-run', is_flag=True) |
83 | 86 | @click.option('--summary', is_flag=True) |
84 | def process_command(command, plugin_id, custom_plugins_folder, dont_run, summary): | |
87 | @click.option('-o', '--output-file', type=click.Path(exists=False)) | |
88 | @click.option('-sh', '--show-output', is_flag=True) | |
89 | def process_command(command, plugin_id, custom_plugins_folder, dont_run, summary, output_file, show_output): | |
85 | 90 | plugins_manager = PluginsManager(custom_plugins_folder) |
86 | 91 | analyzer = CommandAnalyzer(plugins_manager) |
87 | 92 | if plugin_id: |
88 | 93 | plugin = plugins_manager.get_plugin(plugin_id) |
89 | 94 | if not plugin: |
90 | click.echo(click.style(f"Invalid Plugin: {plugin_id}", fg="red")) | |
95 | click.echo(click.style(f"Invalid Plugin: {plugin_id}", fg="red"), err=True) | |
91 | 96 | return |
92 | 97 | else: |
93 | 98 | plugin = analyzer.get_plugin(command) |
94 | 99 | if not plugin: |
95 | click.echo(click.style(f"Failed to detect command: {command}", fg="red")) | |
100 | click.echo(click.style(f"Failed to detect command: {command}", fg="red"), err=True) | |
96 | 101 | return |
97 | 102 | current_path = os.path.abspath(os.getcwd()) |
98 | 103 | modified_command = plugin.processCommandString(getpass.getuser(), current_path, command) |
99 | 104 | if modified_command: |
100 | 105 | command = modified_command |
101 | if not dont_run: | |
102 | color_message = click.style("Running command: ", fg="green") | |
103 | click.echo(f"{color_message} {command}\n") | |
106 | if dont_run: | |
107 | color_message = click.style("Command: ", fg="green") | |
108 | click.echo(f"{color_message} {command}") | |
109 | else: | |
104 | 110 | p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
105 | 111 | output = io.StringIO() |
106 | 112 | while True: |
107 | 113 | retcode = p.poll() |
108 | 114 | line = p.stdout.readline().decode('utf-8') |
109 | sys.stdout.write(line) | |
115 | if show_output: | |
116 | sys.stdout.write(line) | |
110 | 117 | output.write(line) |
111 | 118 | if retcode is not None: |
112 | 119 | extra_lines = map(lambda x: x.decode('utf-8'), p.stdout.readlines()) |
113 | sys.stdout.writelines(line) | |
120 | if show_output: | |
121 | sys.stdout.writelines(line) | |
114 | 122 | output.writelines(extra_lines) |
115 | 123 | break |
116 | 124 | output_value = output.getvalue() |
117 | 125 | if retcode == 0: |
118 | 126 | plugin.processOutput(output_value) |
119 | 127 | if summary: |
120 | click.echo(click.style("\nPlugin Summary: ", fg="cyan")) | |
121 | 128 | click.echo(json.dumps(plugin.get_summary(), indent=4)) |
122 | 129 | else: |
123 | click.echo(click.style("\nFaraday API json: ", fg="cyan")) | |
124 | click.echo(json.dumps(plugin.get_data(), indent=4)) | |
130 | if output_file: | |
131 | with open(output_file, "w") as f: | |
132 | json.dump(plugin.get_data(), f) | |
133 | else: | |
134 | click.echo(json.dumps(plugin.get_data(), indent=4)) | |
125 | 135 | else: |
126 | click.echo(click.style("Command execution error!!", fg="red")) | |
127 | else: | |
128 | color_message = click.style("Command: ", fg="green") | |
129 | click.echo(f"{color_message} {command}") | |
136 | click.echo(click.style("Command execution error!!", fg="red"), err=True) | |
130 | 137 | |
131 | 138 | |
132 | 139 | |
143 | 150 | if plugin: |
144 | 151 | click.echo(click.style(f"Faraday Plugin: {plugin.id}", fg="cyan")) |
145 | 152 | else: |
146 | click.echo(click.style(f"Failed to detect report: {report_file}", fg="red")) | |
153 | click.echo(click.style(f"Failed to detect report: {report_file}", fg="red"), err=True) | |
147 | 154 | |
148 | 155 | |
149 | 156 | @cli.command() |
156 | 163 | if plugin: |
157 | 164 | click.echo(click.style(f"Faraday Plugin: {plugin.id}", fg="cyan")) |
158 | 165 | else: |
159 | click.echo(click.style(f"Failed to detect command: {command}", fg="red")) | |
166 | click.echo(click.style(f"Failed to detect command: {command}", fg="red"), err=True) |
80 | 80 | else: |
81 | 81 | try: |
82 | 82 | for event, elem in ET.iterparse(report_file, ('start',)): |
83 | main_tag = elem.tag | |
83 | prefix, has_namespace, postfix = elem.tag.partition("}") | |
84 | if has_namespace: | |
85 | main_tag = postfix | |
86 | else: | |
87 | main_tag = elem.tag | |
84 | 88 | break |
85 | 89 | logger.debug("Found XML content on file: %s - Main tag: %s", report_path, main_tag) |
86 | 90 | except Exception as e: |
88 | 92 | try: |
89 | 93 | report_file.seek(0) |
90 | 94 | json_data = json.load(report_file) |
91 | file_json_keys = set(json_data.keys()) | |
95 | if isinstance(json_data, list): | |
96 | if len(json_data) > 0: | |
97 | file_json_keys = set(json_data[0].keys()) | |
98 | else: | |
99 | file_json_keys = set(json_data.keys()) | |
92 | 100 | logger.debug("Found JSON content on file: %s - Keys: %s", report_path, file_json_keys) |
93 | 101 | except Exception as e: |
94 | 102 | logger.debug("Non JSON content [%s] - %s", report_path, e) |
97 | 105 | reader_file_string = StringIO(report_file.read().decode('utf-8')) |
98 | 106 | reader = csv.DictReader(reader_file_string) |
99 | 107 | file_csv_headers = set(reader.fieldnames) |
100 | logger.debug("Found JSON content on file: %s - Keys: %s", report_path, file_json_keys) | |
108 | logger.debug("Found CSV content on file: %s - Keys: %s", report_path, file_json_keys) | |
101 | 109 | except Exception as e: |
102 | logger.debug("Non JSON content [%s] - %s", report_path, e) | |
110 | logger.debug("Non CSV content [%s] - %s", report_path, e) | |
103 | 111 | try: |
104 | 112 | file_zip = zipfile.ZipFile(report_path, "r") |
105 | 113 | files_in_zip = set(file_zip.namelist()) |
56 | 56 | self.start_date = datetime.now() |
57 | 57 | self.logger = logger.getChild(self.__class__.__name__) |
58 | 58 | self.open_options = {"mode": "r", "encoding": "utf-8"} |
59 | self.plugin_version = "0.0" | |
59 | 60 | self.vulns_data = {"hosts": [], "command": {"tool": "", |
60 | 61 | "command": "", |
61 | 62 | "params": "", |
315 | 316 | |
316 | 317 | if not hostnames: |
317 | 318 | hostnames = [] |
319 | if not isinstance(hostnames, list): | |
320 | hostnames = [hostnames] | |
318 | 321 | # Some plugins sends a list with None, we filter empty and None values. |
319 | 322 | hostnames = [hostname for hostname in hostnames if hostname] |
320 | 323 | if os is None: |
327 | 330 | "credentials": [], "services": [], "vulnerabilities": [], "tags": tags} |
328 | 331 | host_id = self.save_host_cache(host) |
329 | 332 | return host_id |
330 | ||
331 | # @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5", | |
332 | # current_version=VERSION, | |
333 | # details="Interface object removed. Use host or service instead") | |
334 | def createAndAddInterface( | |
335 | self, host_id, name="", mac="00:00:00:00:00:00", | |
336 | ipv4_address="0.0.0.0", ipv4_mask="0.0.0.0", ipv4_gateway="0.0.0.0", | |
337 | ipv4_dns=None, ipv6_address="0000:0000:0000:0000:0000:0000:0000:0000", | |
338 | ipv6_prefix="00", | |
339 | ipv6_gateway="0000:0000:0000:0000:0000:0000:0000:0000", ipv6_dns=None, | |
340 | network_segment="", hostname_resolution=None): | |
341 | if ipv4_dns is None: | |
342 | ipv4_dns = [] | |
343 | if ipv6_dns is None: | |
344 | ipv6_dns = [] | |
345 | if hostname_resolution is None: | |
346 | hostname_resolution = [] | |
347 | if not isinstance(hostname_resolution, list): | |
348 | self.logger.warning("hostname_resolution parameter must be a list and is (%s)", type(hostname_resolution)) | |
349 | hostname_resolution = [hostname_resolution] | |
350 | # We don't use interface anymore, so return a host id to maintain | |
351 | # backwards compatibility | |
352 | # Little hack because we dont want change all the plugins for add hostnames in Host object. | |
353 | # SHRUG | |
354 | host = self.get_from_cache(host_id) | |
355 | for hostname in hostname_resolution: | |
356 | if hostname not in host["hostnames"]: | |
357 | host["hostnames"].append(hostname) | |
358 | host["mac"] = mac | |
359 | return host_id | |
360 | ||
361 | # @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5", | |
362 | # current_version=VERSION, | |
363 | # details="Interface object removed. Use host or service instead. Service will be attached | |
364 | # to Host!") | |
365 | def createAndAddServiceToInterface(self, host_id, interface_id, name, | |
366 | protocol="tcp", ports=None, | |
367 | status="open", version="unknown", | |
368 | description="", tags=None): | |
369 | return self.createAndAddServiceToHost(host_id, name, protocol, ports, status, version, description, tags) | |
370 | 333 | |
371 | 334 | def createAndAddServiceToHost(self, host_id, name, |
372 | 335 | protocol="tcp", ports=None, |
419 | 382 | vulnerability["run_date"] = self.get_utctimestamp(run_date) |
420 | 383 | vulnerability_id = self.save_host_vuln_cache(host_id, vulnerability) |
421 | 384 | return vulnerability_id |
422 | ||
423 | # @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5", | |
424 | # current_version=VERSION, | |
425 | # details="Interface object removed. Use host or service instead. Vuln will be added | |
426 | # to Host") | |
427 | def createAndAddVulnToInterface(self, host_id, interface_id, name, | |
428 | desc="", ref=None, severity="", | |
429 | resolution="", data="", tags=None): | |
430 | return self.createAndAddVulnToHost(host_id, name, desc=desc, ref=ref, severity=severity, resolution=resolution, | |
431 | data=data, tags=tags) | |
432 | 385 | |
433 | 386 | def createAndAddVulnToService(self, host_id, service_id, name, desc="", |
434 | 387 | ref=None, severity="", resolution="", data="", external_id=None, run_date=None, |
514 | 467 | def createAndAddNoteToHost(self, host_id, name, text): |
515 | 468 | return None |
516 | 469 | |
517 | def createAndAddNoteToInterface(self, host_id, interface_id, name, text): | |
518 | return None | |
519 | 470 | |
520 | 471 | def createAndAddNoteToService(self, host_id, service_id, name, text): |
521 | 472 | return None |
529 | 480 | service["credentials"].append(credential) |
530 | 481 | credential_id = self.save_cache(credential) |
531 | 482 | return credential_id |
532 | ||
533 | def log(self, msg, level='INFO'):# TODO borrar | |
534 | pass | |
535 | #self.__addPendingAction(Modelactions.LOG, msg, level) | |
536 | ||
537 | def devlog(self, msg): # TODO borrar | |
538 | pass | |
539 | #self.__addPendingAction(Modelactions.DEVLOG, msg) | |
540 | 483 | |
541 | 484 | def get_data(self): |
542 | 485 | self.vulns_data["command"]["tool"] = self.id |
651 | 594 | return match |
652 | 595 | |
653 | 596 | |
597 | class PluginMultiLineJsonFormat(PluginByExtension): | |
598 | ||
599 | def __init__(self): | |
600 | super().__init__() | |
601 | self.json_keys = set() | |
602 | self.extension = ".json" | |
603 | ||
604 | def report_belongs_to(self, file_json_keys=None, **kwargs): | |
605 | match = False | |
606 | report_path = kwargs.get("report_path", "") | |
607 | if super().report_belongs_to(**kwargs): | |
608 | with open(report_path) as f: | |
609 | try: | |
610 | json_lines = list(map(lambda x: json.loads(x), f.readlines())) | |
611 | if len(json_lines) > 0: | |
612 | matched_lines = list(filter(lambda json_line: self.json_keys.issubset(json_line.keys()), | |
613 | json_lines)) | |
614 | match = len(matched_lines) == len(json_lines) | |
615 | self.logger.debug("Json Keys Match: [%s =/in %s] -> %s", json_lines[0].keys(), self.json_keys, | |
616 | match) | |
617 | except ValueError: | |
618 | return False | |
619 | return match | |
620 | ||
621 | ||
654 | 622 | class PluginCSVFormat(PluginByExtension): |
655 | 623 | |
656 | 624 | def __init__(self): |
99 | 99 | |
100 | 100 | def resolve_hostname(hostname): |
101 | 101 | try: |
102 | socket.inet_aton(hostname) # is already an ip | |
103 | return hostname | |
104 | except socket.error: | |
105 | pass | |
106 | try: | |
102 | 107 | ip_address = socket.gethostbyname(hostname) |
103 | 108 | except Exception as e: |
104 | 109 | return hostname |
228 | 228 | self._current_output = None |
229 | 229 | self.target = None |
230 | 230 | |
231 | def parseOutputString(self, output, debug=False): | |
231 | def parseOutputString(self, output): | |
232 | 232 | """ |
233 | 233 | This method will discard the output the shell sends, it will read it |
234 | 234 | from the xml where it expects it to be present. |
242 | 242 | if site.ip is None: |
243 | 243 | continue |
244 | 244 | |
245 | if site.host != site.ip: | |
246 | host = site.host | |
247 | h_id = self.createAndAddHost(site.ip, site.os) | |
248 | if site.host is None: | |
249 | i_id = self.createAndAddInterface(h_id, site.ip, ipv4_address=site.ip) | |
245 | if site.host != site.ip and site.host is not None: | |
246 | hostnames = [site.host] | |
250 | 247 | else: |
251 | i_id = self.createAndAddInterface(h_id, site.ip, | |
252 | ipv4_address=site.ip, | |
253 | hostname_resolution=[host]) | |
254 | s_id = self.createAndAddServiceToInterface( | |
248 | hostnames = None | |
249 | h_id = self.createAndAddHost(site.ip, site.os, hostnames=hostnames) | |
250 | s_id = self.createAndAddServiceToHost( | |
255 | 251 | h_id, |
256 | i_id, | |
257 | 252 | "http", |
258 | 253 | "tcp", |
259 | 254 | ports=[site.port], |
36 | 36 | for line in output.split('\n'): |
37 | 37 | if line.startswith('#'): |
38 | 38 | continue |
39 | ||
40 | 39 | fields = self.get_info(line) |
41 | ||
42 | 40 | if len(fields) < 6: |
43 | 41 | continue |
44 | ||
45 | 42 | address = fields[0] |
46 | h_id = self.createAndAddHost(address) | |
47 | ||
48 | 43 | port = fields[1] |
49 | 44 | protocol = fields[2] |
50 | 45 | port_status = fields[3] |
51 | ||
52 | 46 | identification = fields[5] |
53 | 47 | printable_banner = fields[6] |
54 | ||
55 | 48 | if port in services.keys(): |
56 | 49 | if identification != 'unidentified': |
57 | 50 | services[port][5] += ', ' + identification |
66 | 59 | printable_banner, |
67 | 60 | None] |
68 | 61 | |
69 | args = {} | |
70 | ||
71 | if self.args.__getattribute__("6"): | |
72 | self.ip = self.get_ip_6(self.args.m) | |
73 | args['ipv6_address'] = address | |
62 | if address != self.args.m: | |
63 | hostnames = [self.args.m] | |
74 | 64 | else: |
75 | self.ip = resolve_hostname(self.args.m) | |
76 | args['ipv4_address'] = address | |
77 | ||
78 | if address != self.args.m: | |
79 | args['hostname_resolution'] = [self.args.m] | |
80 | ||
81 | i_id = self.createAndAddInterface(h_id, name=address, **args) | |
65 | hostnames = None | |
66 | h_id = self.createAndAddHost(address, hostnames=hostnames) | |
82 | 67 | |
83 | 68 | for key in services: |
84 | 69 | service = services.get(key) |
85 | self.createAndAddServiceToInterface( | |
70 | self.createAndAddServiceToHost( | |
86 | 71 | h_id, |
87 | i_id, | |
88 | 72 | service[5], |
89 | 73 | service[2], |
90 | 74 | ports=[service[1]], |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2017 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | ||
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
6 | 2 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
7 | 3 | from faraday_plugins.plugins.plugins_utils import resolve_hostname |
8 | from lxml import objectify | |
9 | from urllib.parse import urlparse | |
10 | ||
11 | __author__ = "Alejando Parodi, Ezequiel Tavella" | |
4 | try: | |
5 | import xml.etree.cElementTree as ET | |
6 | except ImportError: | |
7 | import xml.etree.ElementTree as ET | |
8 | ||
9 | ||
10 | __author__ = "Alejando Parodi, Ezequiel Tavella, Blas Moyano" | |
12 | 11 | __copyright__ = "Copyright (c) 2015, Infobyte LLC" |
13 | 12 | __credits__ = ["Alejando Parodi", "Ezequiel Tavella"] |
14 | 13 | __license__ = "" |
17 | 16 | __status__ = "Development" |
18 | 17 | |
19 | 18 | |
20 | ||
21 | class AppscanParser(): | |
22 | ||
23 | def __init__(self, output, logger): | |
24 | self.issue_list = [] | |
25 | self.logger = logger | |
26 | self.obj_xml = objectify.fromstring(output) | |
27 | ||
28 | def parse_issues(self): | |
29 | issue_type = self.parse_issue_type() | |
30 | for issue in self.obj_xml["issue-group"]["item"]: | |
31 | issue_data = issue_type[issue['issue-type']['ref']] | |
32 | obj_issue = {} | |
33 | obj_issue["name"] = issue_data["name"] | |
34 | obj_issue['advisory'] = issue_data["advisory"] | |
35 | if "cve" in issue_data: | |
36 | obj_issue['cve'] = issue_data["cve"].text | |
37 | obj_issue['url'] = self.get_url(issue['url']['ref'].text) | |
38 | obj_issue['cvss_score'] = issue["cvss-score"].text | |
39 | obj_issue['response'] = self.get_response(issue) | |
40 | obj_issue['request'] = issue['variant-group']['item']["test-http-traffic"].text | |
41 | obj_issue['method'] = self.get_method(issue['variant-group']['item']["test-http-traffic"].text) | |
42 | obj_issue['severity'] = issue['severity'].text | |
43 | obj_issue['issue-description'] = self.parse_advisory_group(issue_data['advisory']) | |
44 | for recommendation in self.obj_xml["fix-recommendation-group"]["item"]: | |
45 | full_data = "" | |
46 | if recommendation.attrib['id'] == issue_data["fix-recommendation"]: | |
47 | for data in recommendation['general']['fixRecommendation']["text"]: | |
48 | full_data += '' + data | |
49 | obj_issue["recomendation"] = full_data | |
50 | if hasattr(recommendation['general']['fixRecommendation'], 'link'): | |
51 | obj_issue["ref_link"] = recommendation['general']['fixRecommendation']['link'].text | |
52 | self.issue_list.append(obj_issue) | |
53 | return self.issue_list | |
54 | ||
55 | def parse_hosts(self): | |
56 | hosts_list = [] | |
57 | for host in self.obj_xml['scan-configuration']['scanned-hosts']['item']: | |
58 | hosts_dict = {} | |
59 | hosts_dict['ip'] = resolve_hostname(host['host'].text) | |
60 | hosts_dict['hostname'] = host['host'].text | |
61 | hosts_dict['os'] = host['operating-system'].text | |
62 | hosts_dict['port'] = host['port'].text | |
63 | if host['port'].text == '443': | |
64 | hosts_dict['scheme'] = 'https' | |
19 | class AppScanParser: | |
20 | def __init__(self, xml_output): | |
21 | self.tree = self.parse_xml(xml_output) | |
22 | if self.tree: | |
23 | self.operating_system = self.tree.attrib['technology'] | |
24 | url_group = [tags.tag for tags in self.tree] | |
25 | check_url = True if 'url-group' in url_group else False | |
26 | if check_url: | |
27 | self.urls = self.get_urls_info(self.tree.find('url-group')) | |
65 | 28 | else: |
66 | hosts_dict['scheme'] = 'http' | |
67 | hosts_list.append(hosts_dict) | |
68 | return hosts_list | |
69 | ||
70 | def parse_issue_type(self): | |
71 | res = {} | |
72 | for issue_type in self.obj_xml["issue-type-group"]["item"]: | |
73 | res[issue_type.attrib['id']] = { | |
74 | 'name': issue_type.name.text, | |
75 | 'advisory': issue_type["advisory"]["ref"].text, | |
76 | 'fix-recommendation': issue_type["fix-recommendation"]["ref"].text | |
29 | self.urls = None | |
30 | self.layout = self.get_layout_info(self.tree.find('layout')) | |
31 | self.item = self.get_issue_type(self.tree.find('issue-type-group')) | |
32 | self.name_scan = self.get_issue_data(self.tree.find('advisory-group')) | |
33 | self.host_data = None if self.tree.find('scan-configuration/scanned-hosts/item') is None else \ | |
34 | self.get_scan_conf_data(self.tree.find('scan-configuration/scanned-hosts/item')) | |
35 | self.issue_group = self.get_info_issue_group(self.tree.find("issue-group")) | |
36 | self.fix_recomendation = self.get_fix_info(self.tree.find('fix-recommendation-group')) | |
37 | ||
38 | else: | |
39 | self.tree = None | |
40 | ||
41 | def parse_xml(self, xml_output): | |
42 | try: | |
43 | tree = ET.fromstring(xml_output) | |
44 | except SyntaxError as err: | |
45 | print('SyntaxError In xml: %s. %s' % (err, xml_output)) | |
46 | return None | |
47 | return tree | |
48 | ||
49 | def get_fix_info(self, tree): | |
50 | list_fix = [] | |
51 | for item in tree: | |
52 | text_info_join = [] | |
53 | if item.find("general/fixRecommendation"): | |
54 | for text_tag in tree.findall('text'): | |
55 | text_info_join += text_tag.text | |
56 | info_fix = { | |
57 | "id": item.attrib.get('id', None), | |
58 | "text": text_info_join | |
59 | } | |
60 | list_fix.append(info_fix) | |
61 | return list_fix | |
62 | ||
63 | def get_info_issue_group(sef,tree): | |
64 | data_res_req = [] | |
65 | for item in tree: | |
66 | if item.find("variant-group/item/issue-information"): | |
67 | resp = item.find("variant-group/item/issue-information").text | |
68 | else: | |
69 | resp = "Not Response" | |
70 | ||
71 | json_res_req = { | |
72 | "request": "Not request" if item.find("variant-group/item/test-http-traffic") is None else | |
73 | item.find("variant-group/item/test-http-traffic").text, | |
74 | "response": resp, | |
75 | "location": "Not Location" if item.find("location") is None else item.find("location").text, | |
76 | "source_file": "0.0.0.0" if item.find("source-file") is None else item.find("source-file").text, | |
77 | "line": 0 if item.find("line") is None else item.find("line").text, | |
78 | "id_item": item.attrib.get('id', 'Not id item'), | |
79 | "severity": 0 if item.find("severity-id") is None else item.find("severity-id").text, | |
80 | "cvss": "No cvss" if item.find("cvss-score") is None else item.find("cvss-score").text, | |
81 | "cwe": "No cwe" if item.find("cwe") is None else item.find("cwe").text, | |
82 | "remediation": "No remedation" if item.find("remediation/ref") is None else item.find( | |
83 | "remediation/ref").text, | |
84 | "advisory": "No advisory" if item.find("advisory/ref") is None else item.find("advisory/ref").text, | |
85 | "url_id": "No url id" if item.find("url/ref") is None else item.find("url/ref").text, | |
86 | "id_adv": "Not info" if item.find("issue-type/ref") is None else item.find("issue-type/ref").text | |
87 | } | |
88 | ||
89 | data_res_req.append(json_res_req) | |
90 | return data_res_req | |
91 | ||
92 | def get_layout_info(self, tree): | |
93 | info_layout = { | |
94 | "name": "Not info" if tree.find("application-name") is None else tree.find("application-name").text, | |
95 | "date": "Not info" if tree.find("report-date") is None else tree.find("report-date").text, | |
96 | "details": f'Departamento: {"Not info" if tree.find("department") is None else tree.find("department").text}' | |
97 | f'Compania: {"Not info" if tree.find("company") is None else tree.find("company").text}' | |
98 | f'Titulo Reporte: {"Not info" if tree.find("title") is None else tree.find("title").text}', | |
99 | "nro_issues": None if tree.find("total-issues-in-application") is None else tree.find("total-issues-in-application").text, | |
100 | } | |
101 | return info_layout | |
102 | ||
103 | def get_issue_type(self, tree): | |
104 | list_item = [] | |
105 | for item in tree: | |
106 | severity = item.attrib.get('severity-id', None) | |
107 | if severity is None: | |
108 | severity = item.attrib.get('maxIssueSeverity', None) | |
109 | ||
110 | item_info = { | |
111 | "id": item.attrib.get('id', None), | |
112 | "name": item.find("name").text, | |
113 | "severity_id": severity, | |
114 | "severity": item.attrib.get('severity', None), | |
115 | "cwe": "Not info" if item.find("cme") is None else item.find("cwe").text, | |
116 | "xfid": "Not info" if item.find("xfid") is None else item.find("xfid").text, | |
117 | "advisory": "Not info" if item.find("advisory/ref") is None else item.find("advisory/ref").text | |
118 | } | |
119 | list_item.append(item_info) | |
120 | ||
121 | return list_item | |
122 | ||
123 | def get_issue_data(self, tree): | |
124 | list_item_data = [] | |
125 | item_data = {} | |
126 | for item in tree: | |
127 | for adivisory in item: | |
128 | if adivisory.find("cwe/link"): | |
129 | cwe = adivisory.find("cwe/link").text | |
130 | else: | |
131 | cwe = "Not Response" | |
132 | ||
133 | if adivisory.find("xfid/link"): | |
134 | xfid = adivisory.find("xfid/link").text | |
135 | else: | |
136 | xfid = "Not Response" | |
137 | ||
138 | item_data = { | |
139 | "id": item.attrib.get('id', None), | |
140 | "name": "Not info" if adivisory.find("name") is None else adivisory.find("name").text, | |
141 | "description": "Not info" if adivisory.find("testDescription") is None else | |
142 | adivisory.find("testDescription").text, | |
143 | "threatClassification": { | |
144 | "name": "Not info" if adivisory.find("threatClassification/name") is None else | |
145 | adivisory.find("threatClassification/name").text, | |
146 | "reference": "Not info" if adivisory.find("threatClassification/reference") is None else | |
147 | adivisory.find("threatClassification/reference").text, | |
148 | }, | |
149 | "testTechnicalDescription": "Not info" if adivisory.find("testTechnicalDescription") is None else | |
150 | self.get_parser(adivisory.find("testTechnicalDescription")), | |
151 | "testTechnicalDescriptionMixed": "Not info" if adivisory.find("testTechnicalDescriptionMixed") is None else | |
152 | self.get_parser(adivisory.find("testTechnicalDescriptionMixed")), | |
153 | ||
154 | "testDescriptionMixed": "Not info" if adivisory.find("testDescriptionMixed") is None else | |
155 | self.get_parser(adivisory.find("testDescriptionMixed")), | |
156 | "causes": "Not info" if adivisory.find("causes/cause") is None else | |
157 | adivisory.find("causes/cause").text, | |
158 | "securityRisks": "Not info" if adivisory.find("securityRisks/securityRisk") is None else | |
159 | adivisory.find("securityRisks/securityRisk").text, | |
160 | "affectedProducts": "Not info" if adivisory.find("affectedProducts/affectedProduct") is None else | |
161 | adivisory.find("affectedProducts/affectedProduct").text, | |
162 | "cwe": cwe, | |
163 | "xfid": xfid, | |
164 | "references": "Not info" if adivisory.find("references") is None else | |
165 | self.get_parser(adivisory.find("references")), | |
166 | "fixRecommendations": "Not info" if adivisory.find("fixRecommendations/fixRecommendation") is None else | |
167 | self.get_parser(adivisory.find("fixRecommendations/fixRecommendation")) | |
77 | 168 | } |
78 | if "cve" in issue_type: | |
79 | res[issue_type.attrib['id']] = {'cve': issue_type["cve"].text} | |
80 | return res | |
81 | ||
82 | def parse_advisory_group(self, advisory): | |
83 | """ | |
84 | Function that parse advisory-group in order to get the item's description | |
85 | """ | |
86 | for item in self.obj_xml["advisory-group"]["item"]: | |
87 | if item.attrib['id'] == advisory: | |
88 | return item['advisory']['testTechnicalDescription']['text'].text | |
89 | ||
90 | def get_url(self, ref): | |
91 | for item in self.obj_xml['url-group']['item']: | |
92 | if item.attrib['id'] == ref: | |
93 | return item['name'].text | |
94 | ||
95 | def get_method(self, http_traffic): | |
96 | methods_list = ['GET', 'POST', 'PUT', 'DELETE', 'CONNECT', 'PATCH', 'HEAD', 'OPTIONS'] | |
97 | try: | |
98 | if http_traffic: | |
99 | for item in methods_list: | |
100 | if http_traffic.startswith(item): | |
101 | return item | |
102 | except TypeError: | |
103 | return None | |
104 | return None | |
105 | ||
106 | def get_response(self, node): | |
107 | try: | |
108 | response = node['variant-group']['item']['issue-information']["testResponseChunk"].text | |
109 | return response | |
110 | except AttributeError: | |
111 | return None | |
112 | ||
113 | def get_scan_information(self): | |
114 | ||
115 | scan_information = "File: " + self.obj_xml["scan-information"]["scan-file-name"]\ | |
116 | + "\nStart: " + self.obj_xml["scan-information"]["scan-date-and-time"]\ | |
117 | + "\nSoftware: " + self.obj_xml["scan-information"]["product-name"]\ | |
118 | + "\nVersion: " + self.obj_xml["scan-information"]["product-version"]\ | |
119 | + "\nScanner Elapsed time: " + self.obj_xml["scan-summary"]["scan-Duration"] | |
120 | ||
121 | return scan_information | |
122 | ||
123 | ||
124 | class AppscanPlugin(PluginXMLFormat): | |
125 | """ Example plugin to parse Appscan XML report""" | |
126 | ||
169 | list_item_data.append(item_data) | |
170 | return list_item_data | |
171 | ||
172 | def get_parser(self, tree): | |
173 | text_join = "" | |
174 | code_join = "" | |
175 | link_join = "" | |
176 | ||
177 | if tree.tag == 'testTechnicalDescription': | |
178 | ||
179 | for text_info in tree.findall('text'): | |
180 | text_join += text_info.text | |
181 | ||
182 | for code_info in tree.findall('code'): | |
183 | text_join += code_info.text | |
184 | ||
185 | tech_data = { | |
186 | "text": text_join, | |
187 | "code": code_join | |
188 | } | |
189 | ||
190 | elif tree.tag == 'testDescriptionMixed': | |
191 | ||
192 | for text_info in tree.findall('p'): | |
193 | text_join += text_info.text | |
194 | ||
195 | for code_info in tree.findall('li'): | |
196 | text_join += code_info.text | |
197 | ||
198 | tech_data = { | |
199 | "text": text_join, | |
200 | "items": code_join | |
201 | } | |
202 | ||
203 | elif tree.tag == 'testTechnicalDescriptionMixed': | |
204 | ||
205 | for text_info in tree.findall('p'): | |
206 | text_join += text_info.text | |
207 | ||
208 | tech_data = { | |
209 | "text": text_join, | |
210 | } | |
211 | ||
212 | elif tree.tag == 'references': | |
213 | for text_info in tree.findall('text'): | |
214 | text_join += "no info " if text_info.text is None else text_info.text | |
215 | ||
216 | for link_info in tree.findall('link'): | |
217 | link_join += "no info " if link_info.text is None else link_info.text | |
218 | link_join += link_info.attrib.get('target', 'not target') | |
219 | ||
220 | tech_data = { | |
221 | "text": text_join, | |
222 | "Link": link_join | |
223 | } | |
224 | ||
225 | elif tree.tag == 'fixRecommendation': | |
226 | for text_info in tree.findall('text'): | |
227 | text_join += "no info " if text_info.text is None else text_info.text | |
228 | ||
229 | for link_info in tree.findall('link'): | |
230 | link_join += "no info " if link_info.text is None else link_info.text | |
231 | link_join += link_info.attrib.get('target', 'not target') | |
232 | ||
233 | tech_data = { | |
234 | "text": text_join, | |
235 | "link": link_join | |
236 | } | |
237 | ||
238 | return tech_data | |
239 | ||
240 | def get_urls_info(self, tree): | |
241 | list_url = [] | |
242 | for url in tree: | |
243 | url_info = { | |
244 | "id_item": url.attrib.get('id', 'Not id item'), | |
245 | "id": "Not info" if url.find("issue-type") is None else url.find("issue-type").text, | |
246 | "url": "Not info" if url.find("name") is None else url.find("name").text, | |
247 | } | |
248 | list_url.append(url_info) | |
249 | ||
250 | return list_url | |
251 | ||
252 | def get_scan_conf_data(self, host_info): | |
253 | info_host = { | |
254 | "host": "Not info" if host_info.find("host") is None else host_info.find("host").text, | |
255 | "port": "Not info" if host_info.find("port") is None else host_info.find("port").text, | |
256 | "os": "Not info" if host_info.find("operating-system") is None else host_info.find("operating-system").text, | |
257 | "webserver": "Not info" if host_info.find("web-server") is None else host_info.find("web-server").text, | |
258 | "appserver": "Not info" if host_info.find("application-server") is None else host_info.find("application-server").text, | |
259 | } | |
260 | return info_host | |
261 | ||
262 | ||
263 | class AppScanPlugin(PluginXMLFormat): | |
127 | 264 | def __init__(self): |
128 | 265 | super().__init__() |
129 | 266 | self.identifier_tag = "xml-report" |
130 | self.id = "Appscan" | |
131 | self.name = "Appscan XML Plugin" | |
132 | self.plugin_version = "0.0.1" | |
267 | self.id = 'Appscan' | |
268 | self.name = 'Appscan XML Plugin' | |
269 | self.plugin_version = '0.0.1' | |
270 | self.version = '1.0.0' | |
271 | self.framework_version = '1.0.0' | |
133 | 272 | self.options = None |
134 | self.open_options = {"mode": "r", "encoding": "utf-8"} | |
273 | self.protocol = None | |
274 | self.port = '80' | |
275 | self.address = None | |
135 | 276 | |
136 | 277 | def parseOutputString(self, output): |
137 | try: | |
138 | parser = AppscanParser(output, self.logger) | |
139 | issues = parser.parse_issues() | |
140 | scanned_hosts = parser.parse_hosts() | |
141 | hosts_dict = {} | |
142 | for host in scanned_hosts: | |
143 | host_id = self.createAndAddHost(host['ip'], os=host['os'], hostnames=[host['hostname']]) | |
144 | service_id = self.createAndAddServiceToHost( | |
145 | host_id, | |
146 | host['scheme'], | |
147 | ports=[host['port']], | |
148 | protocol="tcp?HTTP") | |
149 | if host['port']: | |
150 | if host['port'] not in ('443', '80'): | |
151 | key_url = f"{host['scheme']}://{host['hostname']}:{host['port']}" | |
152 | else: | |
153 | key_url = f"{host['scheme']}://{host['hostname']}" | |
278 | parser = AppScanParser(output) | |
279 | layout = parser.layout | |
280 | operating_system = parser.operating_system | |
281 | host_data = parser.host_data | |
282 | urls = parser.urls | |
283 | item = parser.item | |
284 | name_scan = parser.name_scan | |
285 | issues = parser.issue_group | |
286 | recomendation = parser.fix_recomendation | |
287 | ||
288 | if operating_system == 'DAST': | |
289 | host_id = self.createAndAddHost(resolve_hostname(host_data['host']), os=host_data['os'], | |
290 | hostnames=[host_data['host']], description=layout['details']) | |
291 | ||
292 | service_id = self.createAndAddServiceToHost(host_id, host_data['host'], ports=host_data['port'], | |
293 | protocol="tcp?HTTP", | |
294 | description=f'{host_data["webserver"]} - {host_data["appserver"]}') | |
295 | if layout['nro_issues'] is None: | |
296 | nro_check = True | |
297 | ||
298 | else: | |
299 | nro_check = False | |
300 | check_issues = [] | |
301 | ||
302 | for issue in issues: | |
303 | id = f"{issue['url_id']}{issue['advisory']}" | |
304 | if id in check_issues and nro_check is True: | |
305 | check_issues.append(id) | |
154 | 306 | else: |
155 | key_url = f"{host['scheme']}://{host['hostname']}" | |
156 | hosts_dict[key_url] = {'host_id': host_id, 'service_id': service_id} | |
157 | for issue in issues: | |
158 | url_parsed = urlparse(issue['url']) | |
159 | url_string = f'{url_parsed.scheme}://{url_parsed.netloc}' | |
160 | for key in hosts_dict: | |
161 | if url_string == key: | |
162 | h_id = hosts_dict[key]['host_id'] | |
163 | s_id = hosts_dict[key]['service_id'] | |
164 | refs = [] | |
165 | if "ref_link" in issue: | |
166 | refs.append(f"Fix link: {issue['ref_link']}" ) | |
167 | if "cvss_score" in issue: | |
168 | refs.append(f"CVSS Score: {issue['cvss_score']}") | |
169 | if "cve" in issue: | |
170 | refs.append(f"CVE: {issue['cve']}") | |
171 | if "advisory" in issue: | |
172 | refs.append(f"Advisory: {issue['advisory']}") | |
173 | self.createAndAddVulnWebToService( | |
174 | h_id, | |
175 | s_id, | |
176 | issue["name"], | |
177 | desc=issue["issue_description"] if "issue_description" in issue else "", | |
178 | ref=refs, | |
179 | severity=issue["severity"], | |
180 | resolution=issue["recomendation"], | |
181 | website=url_parsed.netloc, | |
182 | path=url_parsed.path, | |
183 | request=issue["request"] if "request" in issue else "", | |
184 | response=issue["response"] if issue["response"] else "", | |
185 | method=issue["method"] if issue["method"] else "") | |
186 | except Exception as e: | |
187 | self.logger.error("Parsing Output Error: %s", e) | |
307 | check_issues.append(id) | |
308 | for info in name_scan: | |
309 | if info['id'] == issue['advisory']: | |
310 | vuln_name = info['name'] | |
311 | vuln_desc = info['description'] | |
312 | resolution = "" | |
313 | if 'text' in info['fixRecommendations']: | |
314 | resolution += info['fixRecommendations']['text'] | |
315 | if 'link' in info['fixRecommendations']: | |
316 | resolution += info['fixRecommendations']['link'] | |
317 | ||
318 | vuln_data = f'xfix: {info["xfid"]} cme: {info["cwe"]}' | |
319 | ||
320 | for url in urls: | |
321 | if url['id'] == issue['advisory']: | |
322 | url_name = url['url'] | |
323 | elif url['id_item'] == issue['id_item']: | |
324 | url_name = url['url'] | |
325 | else: | |
326 | url_name = None | |
327 | ||
328 | for rec in recomendation: | |
329 | if rec['id'] == issue['advisory']: | |
330 | vuln_data = f'{vuln_data}, {rec["text"]} ' | |
331 | ||
332 | ref = f'cwe: {issue["cwe"]} cvss: {issue["cvss"]} remediation: {issue["remediation"]}' | |
333 | self.createAndAddVulnWebToService(host_id=host_id, service_id=service_id, name=vuln_name, | |
334 | desc=vuln_desc, severity=issue['severity'], ref=[ref], | |
335 | website=host_data['host'], request=issue['request'], | |
336 | response=issue['response'], method=issue['request'], | |
337 | resolution=resolution, data=vuln_data, path=url_name) | |
338 | ||
339 | elif operating_system == 'SAST': | |
340 | for info_loc_source in issues: | |
341 | source_file = info_loc_source['source_file'] | |
342 | host_id = self.createAndAddHost(source_file, os=operating_system) | |
343 | ref = f'{info_loc_source["location"]} - {info_loc_source["line"]}' | |
344 | for vuln_data in name_scan: | |
345 | if vuln_data['id'] == info_loc_source["id_adv"]: | |
346 | desc = f'desc: {vuln_data["description"]} DescMix {vuln_data["testDescriptionMixed"]}' | |
347 | ||
348 | resolution = f'Fix Recomendarion {vuln_data["fixRecommendations"]}' \ | |
349 | f' - TestTecnical {vuln_data["testTechnicalDescriptionMixed"]}' | |
350 | ||
351 | self.createAndAddVulnToHost(host_id=host_id, | |
352 | name=vuln_data['name'], | |
353 | desc=desc, | |
354 | ref=[ref], | |
355 | severity=info_loc_source['severity'], | |
356 | resolution=resolution, | |
357 | data=f'xfix: {vuln_data["xfid"]} cme: {vuln_data["cwe"]}', | |
358 | run_date=None, | |
359 | ) | |
360 | else: | |
361 | host_id = self.createAndAddHost(layout['name'], os=operating_system) | |
362 | for vulnserv in name_scan: | |
363 | for sev in item: | |
364 | if sev['id'] == vulnserv['id']: | |
365 | info_severity = sev['severity_id'] | |
366 | if vulnserv['description'] is None: | |
367 | desc = "" | |
368 | else: | |
369 | desc = vulnserv['description'] | |
370 | ||
371 | text_info = vulnserv['fixRecommendations']['text'] \ | |
372 | if 'text' in vulnserv['fixRecommendations'] else "Not Text" | |
373 | ||
374 | link_info = vulnserv['fixRecommendations']['link'] \ | |
375 | if 'link' in vulnserv['fixRecommendations'] else "Not Info" | |
376 | ||
377 | resolution = f"Text:{text_info}. Link: {link_info}." | |
378 | ||
379 | self.createAndAddVulnToHost(host_id=host_id, name=vulnserv['name'], desc=desc, | |
380 | severity=info_severity, resolution=resolution, | |
381 | data=f'xfix: {vulnserv["xfid"]} cme: {vulnserv["cwe"]}', run_date=None) | |
188 | 382 | |
189 | 383 | |
190 | 384 | def createPlugin(): |
191 | return AppscanPlugin() | |
192 | ||
193 | # I'm Py3 | |
385 | return AppScanPlugin() |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | """ | |
3 | Faraday Penetration Test IDE | |
4 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
5 | See the file 'doc/LICENSE' for the license information | |
6 | """ | |
7 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
8 | from datetime import datetime | |
9 | try: | |
10 | import xml.etree.cElementTree as ET | |
11 | except ImportError: | |
12 | import xml.etree.ElementTree as ET | |
13 | ||
14 | __author__ = 'Blas Moyano' | |
15 | __copyright__ = 'Copyright 2020, Faraday Project' | |
16 | __credits__ = ['Blas Moyano'] | |
17 | __license__ = '' | |
18 | __version__ = '1.0.0' | |
19 | __status__ = 'Development' | |
20 | ||
21 | ||
22 | class AppSpiderParser: | |
23 | def __init__(self, xml_output): | |
24 | self.tree = self.parse_xml(xml_output) | |
25 | if self.tree: | |
26 | self.vuln_list = self.tree.find('VulnList') | |
27 | self.name_scan = self.tree.find('ScanName').text | |
28 | else: | |
29 | self.tree = None | |
30 | ||
31 | def parse_xml(self, xml_output): | |
32 | try: | |
33 | tree = ET.fromstring(xml_output) | |
34 | except SyntaxError as err: | |
35 | print('SyntaxError In xml: %s. %s' % (err, xml_output)) | |
36 | return None | |
37 | return tree | |
38 | ||
39 | ||
40 | class AppSpiderPlugin(PluginXMLFormat): | |
41 | def __init__(self): | |
42 | super().__init__() | |
43 | self.identifier_tag = ["VulnSummary"] | |
44 | self.id = 'AppSpider' | |
45 | self.name = 'AppSpider XML Output Plugin' | |
46 | self.plugin_version = '1.0.0' | |
47 | self.version = '1.0.0' | |
48 | self.framework_version = '1.0.0' | |
49 | self.options = None | |
50 | self.protocol = None | |
51 | self.port = '80' | |
52 | self.address = None | |
53 | ||
54 | def parseOutputString(self, output): | |
55 | parser = AppSpiderParser(output) | |
56 | websites = [] | |
57 | websites_ip = [] | |
58 | ||
59 | for vuln in parser.vuln_list: | |
60 | websites.append(vuln.find('WebSite').text) | |
61 | websites_ip.append(vuln.find('WebSiteIP').text) | |
62 | ||
63 | url = set(websites) | |
64 | ip = set(websites_ip) | |
65 | if None in ip: | |
66 | ip.remove(None) | |
67 | ||
68 | host_id = self.createAndAddHost(name=sorted(list(ip))[0], hostnames=sorted(list(url)), | |
69 | description=parser.name_scan) | |
70 | data_info = [] | |
71 | ||
72 | for vulns in parser.vuln_list: | |
73 | vuln_name = vulns.find('VulnType').text | |
74 | vuln_desc = vulns.find('Description').text | |
75 | vuln_ref = vulns.find('VulnUrl').text | |
76 | severity = vulns.find('AttackScore').text | |
77 | vuln_resolution = vulns.find('Recommendation').text | |
78 | vuln_external_id = vulns.find('DbId').text | |
79 | vuln_run_date = vulns.find('ScanDate').text | |
80 | data_info.append(vulns.find('AttackClass').text) | |
81 | data_info.append(vulns.find('CweId').text) | |
82 | data_info.append(vulns.find('CAPEC').text) | |
83 | data_info.append(vulns.find('DISSA_ASC').text) | |
84 | data_info.append(vulns.find('OWASP2007').text) | |
85 | data_info.append(vulns.find('OWASP2010').text) | |
86 | data_info.append(vulns.find('OWASP2013').text) | |
87 | data_info.append(vulns.find('OVAL').text) | |
88 | data_info.append(vulns.find('WASC').text) | |
89 | ||
90 | if severity == '1-Informational': | |
91 | severity = 0 | |
92 | elif severity == '2-Low': | |
93 | severity = 1 | |
94 | elif severity == '3-Medium': | |
95 | severity = 2 | |
96 | elif severity == '4-High': | |
97 | severity = 3 | |
98 | else: | |
99 | severity = 10 | |
100 | ||
101 | str_data = f'AttackClass: {data_info[0]}, CweId: {data_info[1]}, CAPEC: {data_info[2]}, ' \ | |
102 | f'DISSA_ASC: {data_info[3]}, OWASP2007: {data_info[4]}, OWASP2010: {data_info[5]}, ' \ | |
103 | f'OWASP2013: {data_info[6]}, OVAL: {data_info[7]}, WASC: {data_info[8]}' | |
104 | ||
105 | if vuln_run_date is None: | |
106 | vuln_run_date = None | |
107 | else: | |
108 | vuln_run_date = datetime.strptime(vuln_run_date, '%Y-%m-%d %H:%M:%S') | |
109 | ||
110 | self.createAndAddVulnToHost(host_id=host_id, name=vuln_name, desc=vuln_desc, ref=[vuln_ref], | |
111 | severity=severity, resolution=vuln_resolution, run_date=vuln_run_date, | |
112 | external_id=vuln_external_id, data=str_data) | |
113 | ||
114 | ||
115 | def createPlugin(): | |
116 | return AppSpiderPlugin() |
54 | 54 | |
55 | 55 | def getSystem(self, tree): |
56 | 56 | system_tree = tree.find('system') |
57 | return System(system_tree) | |
57 | if system_tree is None: | |
58 | return System(tree, False) | |
59 | else: | |
60 | return System(system_tree, True) | |
58 | 61 | |
59 | 62 | |
60 | 63 | class Issue(): |
61 | 64 | |
62 | 65 | def __init__(self, issue_node): |
63 | ||
64 | 66 | self.node = issue_node |
65 | 67 | self.name = self.getDesc('name') |
66 | 68 | self.severity = self.getDesc('severity') |
130 | 132 | except: |
131 | 133 | parameters = '' |
132 | 134 | |
133 | ||
134 | 135 | return ' - '.join(result) |
135 | 136 | |
136 | 137 | def getRequest(self): |
160 | 161 | |
161 | 162 | class System(): |
162 | 163 | |
163 | def __init__(self, node): | |
164 | ||
164 | def __init__(self, node, tag_exists): | |
165 | 165 | self.node = node |
166 | self.user_agent = None | |
167 | self.url = None | |
168 | self.audited_elements = None | |
169 | self.modules = '' | |
170 | self.cookies = None | |
171 | ||
172 | self.getOptions() | |
173 | ||
174 | self.version = self.getDesc('version') | |
175 | self.start_time = self.getDesc('start_datetime') | |
176 | self.finish_time = self.getDesc('finish_datetime') | |
177 | ||
178 | self.note = self.getNote() | |
166 | if not tag_exists: | |
167 | self.user_agent = 'Arachni' | |
168 | self.url = self.getUrl() | |
169 | self.modules = '' | |
170 | self.version = self.node.find('version') | |
171 | self.start_time = self.node.find('start_datetime') | |
172 | self.finish_time = self.node.find('finish_datetime') | |
173 | else: | |
174 | self.user_agent = None | |
175 | self.url = None | |
176 | self.audited_elements = None | |
177 | self.modules = '' | |
178 | self.cookies = None | |
179 | self.getOptions() | |
180 | self.version = self.getDesc('version') | |
181 | self.start_time = self.getDesc('start_datetime') | |
182 | self.finish_time = self.getDesc('finish_datetime') | |
183 | ||
184 | self.note = self.getNote() | |
185 | ||
186 | def getUrl(self): | |
187 | sitemap = self.node.find("sitemap/entry") | |
188 | return sitemap.get('url') | |
179 | 189 | |
180 | 190 | def getOptions(self): |
181 | 191 | |
182 | 192 | # Get values of options scan |
183 | options = self.node.find('options') | |
193 | try: | |
194 | options = self.node.find('options') | |
195 | except: | |
196 | options = False | |
184 | 197 | if options: |
185 | 198 | options_string = options.text |
186 | 199 | else: |
187 | 200 | options_string = None |
188 | 201 | |
189 | self.user_agent = self.node.find('user_agent').text | |
202 | ||
203 | try: | |
204 | self.user_agent = self.node.find('user_agent').text | |
205 | except: | |
206 | self.user_agent = None | |
207 | ||
190 | 208 | self.url = self.node.find('url').text |
191 | 209 | tags_audited_elements = self.node.find('audited_elements') |
192 | 210 | element_text = [] |
238 | 256 | self.ip = plugins_node.find('resolver').find('results') \ |
239 | 257 | .find('hostname').get('ipaddress') |
240 | 258 | except Exception: |
241 | self.ip = '0.0.0.0' | |
259 | self.ip = None | |
242 | 260 | |
243 | 261 | def getHealthmap(self): |
244 | 262 | |
363 | 381 | except Exception as e: |
364 | 382 | self.logger.error("Error on delete file: (%s) [%s]", filename, e) |
365 | 383 | |
366 | def parseOutputString(self, output, debug=False): | |
384 | def parseOutputString(self, output): | |
367 | 385 | """ |
368 | 386 | This method will discard the output the shell sends, it will read it |
369 | 387 | from the xml where it expects it to be present. |
375 | 393 | return |
376 | 394 | |
377 | 395 | self.hostname = self.getHostname(parser.system.url) |
378 | self.address = resolve_hostname(parser.plugins.ip) | |
396 | if parser.plugins.ip: | |
397 | self.address = resolve_hostname(parser.plugins.ip) | |
398 | else: | |
399 | self.address = resolve_hostname(self.hostname) | |
379 | 400 | |
380 | 401 | # Create host and interface |
381 | host_id = self.createAndAddHost(self.address) | |
382 | ||
383 | interface_id = self.createAndAddInterface( | |
402 | host_id = self.createAndAddHost(self.address, hostnames=[self.hostname]) | |
403 | ||
404 | # Create service | |
405 | service_id = self.createAndAddServiceToHost( | |
384 | 406 | host_id, |
385 | self.address, | |
386 | ipv4_address=self.address, | |
387 | hostname_resolution=[self.hostname]) | |
388 | ||
389 | # Create service | |
390 | service_id = self.createAndAddServiceToInterface( | |
391 | host_id, | |
392 | interface_id, | |
393 | 407 | self.protocol, |
394 | 408 | 'tcp', |
395 | 409 | ports=[self.port], |
53 | 53 | if len(vals[0].split(".")) == 4: |
54 | 54 | |
55 | 55 | host = vals[0] |
56 | h_id = self.createAndAddHost(host) | |
57 | i_id = self.createAndAddInterface(h_id, host, ipv4_address=host, mac=vals[1]) | |
56 | h_id = self.createAndAddHost(host, mac=vals[1]) | |
58 | 57 | |
59 | 58 | return True |
60 | 59 |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | import socket | |
7 | import json | |
8 | from datetime import datetime | |
9 | import re | |
10 | from faraday_plugins.plugins.plugin import PluginMultiLineJsonFormat | |
11 | ||
12 | __author__ = "Blas Moyano" | |
13 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
14 | __credits__ = ["Blas Moyano"] | |
15 | __license__ = "" | |
16 | __version__ = "0.0.1" | |
17 | __maintainer__ = "Blas Moyano" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class AwsProwlerJsonParser: | |
23 | ||
24 | def __init__(self, json_output): | |
25 | string_manipulate = json_output.replace("}", "} #") | |
26 | string_manipulate = string_manipulate[:len(string_manipulate) - 2] | |
27 | self.report_aws = string_manipulate.split("#") | |
28 | ||
29 | ||
30 | class AwsProwlerPlugin(PluginMultiLineJsonFormat): | |
31 | """ Handle the AWS Prowler tool. Detects the output of the tool | |
32 | and adds the information to Faraday. | |
33 | """ | |
34 | ||
35 | def __init__(self): | |
36 | super().__init__() | |
37 | self.id = "awsprowler" | |
38 | self.name = "AWS Prowler" | |
39 | self.plugin_version = "0.1" | |
40 | self.version = "0.0.1" | |
41 | self.json_keys = {"Profile", "Account Number"} | |
42 | ||
43 | ||
44 | def parseOutputString(self, output, debug=False): | |
45 | parser = AwsProwlerJsonParser(output) | |
46 | region_list = [] | |
47 | for region in parser.report_aws: | |
48 | json_reg = json.loads(region) | |
49 | region_list.append(json_reg.get('Region', 'Not Info')) | |
50 | ||
51 | host_id = self.createAndAddHost(name=f'{self.name} - {region_list}', description="AWS Prowler") | |
52 | ||
53 | for vuln in parser.report_aws: | |
54 | json_vuln = json.loads(vuln) | |
55 | vuln_name = json_vuln.get('Control', 'Not Info') | |
56 | vuln_desc = json_vuln.get('Message', 'Not Info') | |
57 | vuln_severity = json_vuln.get('Level', 'Not Info') | |
58 | vuln_run_date = json_vuln.get('Timestamp', 'Not Info') | |
59 | vuln_external_id = json_vuln.get('Control ID', 'Not Info') | |
60 | vuln_policy = f'{vuln_name}:{vuln_external_id}' | |
61 | vuln_run_date = vuln_run_date.replace('T', ' ') | |
62 | vuln_run_date = vuln_run_date.replace('Z', '') | |
63 | self.createAndAddVulnToHost(host_id=host_id, name=vuln_name, desc=vuln_desc, | |
64 | severity=self.normalize_severity(vuln_severity), | |
65 | run_date=datetime.strptime(vuln_run_date, '%Y-%m-%d %H:%M:%S'), | |
66 | external_id=vuln_external_id, policyviolations=[vuln_policy]) | |
67 | ||
68 | ||
69 | def createPlugin(): | |
70 | return AwsProwlerPlugin() |
39 | 39 | "Authkey", str, "c818c7798ae1da38b45a6406c8dd0d6d4d007098") |
40 | 40 | self.addSetting("Enable", str, "0") |
41 | 41 | |
42 | def parseOutputString(self, output, debug=False): | |
42 | def parseOutputString(self, output): | |
43 | 43 | """ |
44 | 44 | This method will discard the output the shell sends, it will read it from |
45 | 45 | the xml where it expects it to be present. |
26 | 26 | self._command_regex = re.compile(r'^(sudo brutexss|brutexss|sudo brutexss\.py|brutexss\.py|python brutexss\.py|' |
27 | 27 | r'\.\/brutexss\.py)\s+.*?') |
28 | 28 | |
29 | def parseOutputString(self, output, debug=False): | |
29 | def parseOutputString(self, output): | |
30 | 30 | lineas = output.split("\n") |
31 | 31 | parametro = [] |
32 | 32 | found_vuln = False |
43 | 43 | vuln_list = re.findall("\w+", linea) |
44 | 44 | if vuln_list[2] == "Vulnerable": |
45 | 45 | parametro.append(vuln_list[1]) |
46 | found_vuln=len(parametro) > 0 | |
47 | host_id = self.createAndAddHost(url) | |
46 | found_vuln = len(parametro) > 0 | |
48 | 47 | address = resolve_hostname(url) |
49 | interface_id = self.createAndAddInterface(host_id, address, ipv4_address=address, | |
50 | hostname_resolution=[url]) | |
51 | service_id = self.createAndAddServiceToInterface(host_id, interface_id, self.protocol, 'tcp', | |
52 | ports=[port], status='Open', version="", | |
53 | description="") | |
48 | host_id = self.createAndAddHost(url, hostnames=[url]) | |
49 | service_id = self.createAndAddServiceToHost(host_id, self.protocol, 'tcp', | |
50 | ports=[port], status='Open', version="", | |
51 | description="") | |
54 | 52 | if found_vuln: |
55 | 53 | self.createAndAddVulnWebToService(host_id, service_id, name="xss", desc="XSS", ref='', severity='med', |
56 | 54 | website=url, path='', method='', pname='', params=''.join(parametro), |
222 | 222 | self.target = None |
223 | 223 | |
224 | 224 | |
225 | def parseOutputString(self, output, debug=False): | |
225 | def parseOutputString(self, output): | |
226 | 226 | |
227 | 227 | parser = BurpXmlParser(output) |
228 | 228 | for item in parser.items: |
229 | 229 | |
230 | h_id = self.createAndAddHost(item.ip) | |
231 | ||
232 | i_id = self.createAndAddInterface( | |
230 | h_id = self.createAndAddHost(item.ip, hostnames=[item.host]) | |
231 | s_id = self.createAndAddServiceToHost( | |
233 | 232 | h_id, |
234 | item.ip, | |
235 | ipv4_address=item.ip, | |
236 | hostname_resolution=[item.host]) | |
237 | ||
238 | s_id = self.createAndAddServiceToInterface( | |
239 | h_id, | |
240 | i_id, | |
241 | 233 | item.protocol, |
242 | 234 | "tcp", |
243 | 235 | ports=[str(item.port)], |
105 | 105 | else: |
106 | 106 | port = 0 |
107 | 107 | project_name = 'ProjectName' in parser.cx_xml_results_attribs |
108 | if project_name: | |
109 | host_id = self.createAndAddHost(url.hostname, hostnames=[url.hostname]) | |
110 | interface_id = self.createAndAddInterface(host_id, url.hostname, ipv4_address=url.hostname, | |
111 | hostname_resolution=[url.netloc]) | |
112 | service_to_interface = self.createAndAddServiceToInterface(host_id, interface_id, name=url.scheme, | |
113 | ports=port) | |
114 | else: | |
115 | host_id = self.createAndAddHost(url.hostname, hostnames=[url.hostname]) | |
116 | interface_id = self.createAndAddInterface(host_id, url.hostname, ipv4_address=url.hostname, | |
117 | hostname_resolution=[url.netloc]) | |
118 | service_to_interface = self.createAndAddServiceToInterface(host_id, interface_id, name=url.scheme, | |
119 | ports=port) | |
108 | host_id = self.createAndAddHost(url.hostname, hostnames=[url.hostname, url.netloc]) | |
109 | service_to_interface = self.createAndAddServiceToHost(host_id, name=url.scheme, ports=port) | |
120 | 110 | for vulns in parser.query: |
121 | 111 | refs = [] |
122 | 112 | categories = 'categories' in vulns.query_attrib |
66 | 66 | ip_address = resolve_hostname(domain) |
67 | 67 | |
68 | 68 | # Create host |
69 | host_id = self.createAndAddHost(ip_address) | |
70 | ||
71 | # create interface (special if type "AAAA") | |
72 | if result.get(u"type") == u"AAAA": # AAAA = IPv6 address | |
73 | # TODO is there a function to dynamically update the paramter ipv6_address of an already-created interface? | |
74 | ipv6_address = result.get(u"data")[0] | |
75 | interface_id = self.createAndAddInterface( | |
76 | host_id, | |
77 | ip_address, | |
78 | ipv4_address=ip_address, | |
79 | ipv6_address=ipv6_address, | |
80 | hostname_resolution=[domain]) | |
81 | else: | |
82 | interface_id = self.createAndAddInterface( | |
83 | host_id, | |
84 | ip_address, | |
85 | ipv4_address=ip_address, | |
86 | hostname_resolution=[domain]) | |
69 | host_id = self.createAndAddHost(ip_address, hostnames=[domain]) | |
87 | 70 | |
88 | 71 | |
89 | 72 | # all other TYPES that aren't 'A' and 'AAAA' are dealt here: |
91 | 74 | mx_priority = result.get(u"data")[0] |
92 | 75 | mx_record = result.get(u"data")[1] |
93 | 76 | |
94 | service_id = self.createAndAddServiceToInterface( | |
77 | service_id = self.createAndAddServiceToHost( | |
95 | 78 | host_id=host_id, |
96 | interface_id=interface_id, | |
97 | 79 | name=mx_record, |
98 | 80 | protocol="SMTP", |
99 | 81 | ports=[25], |
108 | 90 | |
109 | 91 | elif result.get(u"type") == u"NS": # Name server record |
110 | 92 | ns_record = result.get(u"data")[0] |
111 | self.createAndAddServiceToInterface( | |
112 | host_id=host_id, | |
113 | interface_id=interface_id, | |
93 | self.createAndAddServiceToHost( | |
114 | 94 | name=ns_record, |
115 | 95 | protocol="DNS", |
116 | 96 | ports=[53], |
125 | 105 | upper_limit_time = result.get(u"data")[5] |
126 | 106 | negative_result_ttl = result.get(u"data")[6] |
127 | 107 | |
128 | service_id = self.createAndAddServiceToInterface( | |
108 | service_id = self.createAndAddServiceToHost( | |
129 | 109 | host_id=host_id, |
130 | interface_id=interface_id, | |
131 | 110 | name=ns_record, |
132 | 111 | protocol="DNS", |
133 | 112 | ports=[53], |
87 | 87 | puerto = self.getPort(url.group(1), proto) |
88 | 88 | |
89 | 89 | host_id = self.createAndAddHost(ip) |
90 | iface_id = self.createAndAddInterface(host_id, ip, ipv4_address = ip) | |
91 | ||
92 | serv_id = self.createAndAddServiceToInterface(host_id, iface_id, proto, protocol=proto, ports=[puerto], | |
93 | status=status) | |
94 | ||
90 | serv_id = self.createAndAddServiceToHost(host_id, proto, protocol=proto, ports=[puerto], status=status) | |
95 | 91 | if len(self.text) > 0: |
96 | 92 | self.createAndAddVulnWebToService(host_id, serv_id, 'Url Fuzzing', severity=0, desc=self.text, |
97 | 93 | website=domain) |
98 | ||
99 | 94 | if len(paths) > 0: |
100 | 95 | self.createAndAddVulnWebToService(host_id, serv_id, "Directory Listing", severity="med", website=domain, |
101 | 96 | request=paths, method="GET") |
102 | ||
103 | 97 | return True |
104 | 98 | |
105 | 99 | def processCommandString(self, username, current_path, command_string): |
77 | 77 | try: |
78 | 78 | data = json.loads(contents) |
79 | 79 | except ValueError: |
80 | self.log('Error parsing report. Make sure the file has valid ' | |
80 | self.logger.error('Error parsing report. Make sure the file has valid ' | |
81 | 81 | 'JSON', 'ERROR') |
82 | 82 | return |
83 | 83 | for (base_url, items) in data.items(): |
84 | 84 | base_split = urlparse.urlsplit(base_url) |
85 | 85 | ip = resolve_hostname(base_split.hostname) |
86 | h_id = self.createAndAddHost(ip) | |
87 | ||
88 | i_id = self.createAndAddInterface( | |
86 | h_id = self.createAndAddHost(ip, hostnames=[base_split.hostname]) | |
87 | s_id = self.createAndAddServiceToHost( | |
89 | 88 | h_id, |
90 | name=ip, | |
91 | ipv4_address=ip, | |
92 | hostname_resolution=[base_split.hostname]) | |
93 | ||
94 | s_id = self.createAndAddServiceToInterface( | |
95 | h_id, | |
96 | i_id, | |
97 | 89 | base_split.scheme, |
98 | 90 | 'tcp', |
99 | 91 | [base_split.port], |
163 | 163 | r'^(sudo dnsenum|dnsenum|sudo dnsenum\.pl|dnsenum\.pl|perl dnsenum\.pl|\.\/dnsenum\.pl)\s+.*?') |
164 | 164 | self.xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") |
165 | 165 | |
166 | def parseOutputString(self, output, debug=False): | |
166 | def parseOutputString(self, output): | |
167 | 167 | """ |
168 | 168 | This method will discard the output the shell sends, it will read it from |
169 | 169 | the xml where it expects it to be present. |
175 | 175 | parser = DnsenumXmlParser(output) |
176 | 176 | |
177 | 177 | for item in parser.items: |
178 | h_id = self.createAndAddHost(item.ip) | |
179 | i_id = self.createAndAddInterface( | |
180 | h_id, | |
181 | item.ip, | |
182 | ipv4_address=item.ip, | |
183 | hostname_resolution=[item.hostname]) | |
178 | h_id = self.createAndAddHost(item.ip, hostnames=[item.hostname]) | |
184 | 179 | |
185 | 180 | del parser |
186 | 181 |
110 | 110 | else: |
111 | 111 | return False |
112 | 112 | |
113 | def parseOutputString(self, output, debug=False): | |
113 | def parseOutputString(self, output): | |
114 | 114 | """ |
115 | 115 | This method will discard the output the shell sends, it will read it |
116 | 116 | from the xml where it expects it to be present. |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | 0 | """ |
4 | 1 | Faraday Penetration Test IDE |
5 | 2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
177 | 174 | hosts = list(filter(lambda h: h.type in valid_records, hosts)) |
178 | 175 | return hosts |
179 | 176 | |
180 | def parseOutputString(self, output, debug=False): | |
177 | def parseOutputString(self, output): | |
181 | 178 | """ |
182 | 179 | This method will discard the output the shell sends, it will read it from |
183 | 180 | the xml where it expects it to be present. |
197 | 194 | elif host.type == "A": |
198 | 195 | hostname = host.name |
199 | 196 | |
200 | h_id = self.createAndAddHost(host.address) | |
201 | ||
202 | if self._isIPV4(str(host.address)): | |
203 | i_id = self.createAndAddInterface( | |
197 | h_id = self.createAndAddHost(host.address, hostnames=[hostname]) | |
198 | if host.type == "info": | |
199 | s_id = self.createAndAddServiceToHost( | |
204 | 200 | h_id, |
205 | name=host.address, | |
206 | ipv4_address=host.address, | |
207 | hostname_resolution=[hostname]) | |
208 | else: | |
209 | i_id = self.createAndAddInterface( | |
210 | h_id, | |
211 | name=host.address, | |
212 | ipv6_address=host.address, | |
213 | hostname_resolution=[hostname]) | |
214 | ||
215 | if host.type == "info": | |
216 | ||
217 | s_id = self.createAndAddServiceToInterface( | |
218 | h_id, | |
219 | i_id, | |
220 | 201 | "domain", |
221 | 202 | protocol="tcp", |
222 | 203 | ports=["53"], |
86 | 86 | else: |
87 | 87 | return False |
88 | 88 | |
89 | def parseOutputString(self, output, debug=False): | |
89 | def parseOutputString(self, output): | |
90 | 90 | """ |
91 | 91 | output is the shell output of command Dnswalk. |
92 | 92 | """ |
93 | 93 | parser = DnswalkParser(output) |
94 | 94 | |
95 | 95 | for item in parser.items: |
96 | ||
97 | 96 | if item['type'] == "A": |
98 | ||
99 | h_id = self.createAndAddHost(item['ip']) | |
100 | i_id = self.createAndAddInterface( | |
97 | h_id = self.createAndAddHost(item['ip'], hostnames=[item['host']]) | |
98 | elif item['type'] == "info": | |
99 | h_id = self.createAndAddHost(item['ip'], hostnames=[item['host']]) | |
100 | s_id = self.createAndAddServiceToHost( | |
101 | 101 | h_id, |
102 | item['ip'], | |
103 | ipv4_address=item['ip'], | |
104 | hostname_resolution=[item['host']]) | |
105 | ||
106 | elif item['type'] == "info": | |
107 | ||
108 | h_id = self.createAndAddHost(item['ip']) | |
109 | ||
110 | i_id = self.createAndAddInterface( | |
111 | h_id, | |
112 | item['ip'], | |
113 | ipv4_address=item['ip'], | |
114 | hostname_resolution=[item['host']]) | |
115 | ||
116 | s_id = self.createAndAddServiceToInterface( | |
117 | h_id, | |
118 | i_id, | |
119 | 102 | "domain", |
120 | 103 | "tcp", |
121 | 104 | ports=['53']) |
122 | ||
123 | 105 | self.createAndAddVulnToService( |
124 | 106 | h_id, |
125 | 107 | s_id, |
258 | 258 | with open(filename, **self.open_options) as output: |
259 | 259 | self.parseOutputString(output) |
260 | 260 | |
261 | def parseOutputString(self, output, debug=False): | |
261 | def parseOutputString(self, output): | |
262 | 262 | parser = CSVParser(output, self.logger) |
263 | 263 | |
264 | 264 | for item in parser.items: |
131 | 131 | pass |
132 | 132 | return item |
133 | 133 | |
134 | def parseOutputString(self, output, debug=False): | |
134 | def parseOutputString(self, output): | |
135 | 135 | |
136 | 136 | parser = FierceParser(output) |
137 | 137 | for item in parser.items: |
73 | 73 | severity=vuln_data['severity'] |
74 | 74 | ) |
75 | 75 | |
76 | def parseOutputString(self, output, debug=False): | |
76 | def parseOutputString(self, output): | |
77 | 77 | fp = FortifyParser(output) |
78 | 78 | if fp.fvdl is not None: |
79 | 79 | self._process_fvdl_vulns(fp) |
55 | 55 | return 5 |
56 | 56 | |
57 | 57 | def createHostInterfaceVuln(self, ip_address, macaddress, hostname, desc, vuln_name, severity): |
58 | h_id = self.createAndAddHost(ip_address) | |
59 | if self._isIPV4(ip_address): | |
60 | i_id = self.createAndAddInterface( | |
61 | h_id, | |
62 | ip_address, | |
63 | macaddress, | |
64 | ipv4_address=ip_address, | |
65 | hostname_resolution=[hostname] | |
66 | ) | |
67 | else: | |
68 | self.createAndAddInterface( | |
69 | h_id, ip_address, ipv6_address=ip_address, hostname_resolution=[hostname]) | |
58 | h_id = self.createAndAddHost(ip_address, hostnames=[hostname]) | |
70 | 59 | |
71 | 60 | self.createAndAddVulnToHost( |
72 | 61 | h_id, |
76 | 65 | severity=severity |
77 | 66 | ) |
78 | 67 | |
79 | def parseOutputString(self, output, debug=False): | |
68 | def parseOutputString(self, output): | |
80 | 69 | |
81 | 70 | try: |
82 | 71 | output = json.loads(output) |
43 | 43 | |
44 | 44 | |
45 | 45 | |
46 | def parseOutputString(self, output, debug=False): | |
46 | def parseOutputString(self, output): | |
47 | 47 | |
48 | 48 | host_info = re.search(r"Connected to (.+)\.", output) |
49 | 49 | banner = re.search("220?([\w\W]+)$", output) |
51 | 51 | hostname = host_info.group(1) |
52 | 52 | ip_address = resolve_hostname(hostname) |
53 | 53 | self._version = banner.groups(0) if banner else "" |
54 | if debug: | |
55 | print(ip_address) | |
56 | ||
57 | h_id = self.createAndAddHost(ip_address) | |
58 | ||
59 | i_id = self.createAndAddInterface( | |
54 | h_id = self.createAndAddHost(ip_address, hostnames=[hostname]) | |
55 | s_id = self.createAndAddServiceToHost( | |
60 | 56 | h_id, |
61 | ip_address, | |
62 | ipv4_address=ip_address, | |
63 | hostname_resolution=[hostname]) | |
64 | ||
65 | s_id = self.createAndAddServiceToInterface( | |
66 | h_id, | |
67 | i_id, | |
68 | 57 | "ftp", |
69 | 58 | "tcp", |
70 | 59 | ports=[self._port], |
71 | 60 | status="open") |
72 | ||
73 | if debug is True: | |
74 | self.logger.info("Debug is active") | |
75 | ||
76 | 61 | return True |
77 | 62 | |
78 | 63 | def processCommandString(self, username, current_path, command_string): |
28 | 28 | |
29 | 29 | self._command_regex = re.compile(r'^(sudo hping3|hping3)\s+.*$') |
30 | 30 | |
31 | def parseOutputString(self, output, debug=False): | |
31 | def parseOutputString(self, output): | |
32 | 32 | |
33 | 33 | regex_ipv4 = re.search(r"(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}" |
34 | 34 | r"|2[0-4][0-9]|25[0-5])\)\:", output) |
40 | 40 | return |
41 | 41 | |
42 | 42 | hostname = output.split(" ")[1] |
43 | host_id = self.createAndAddHost(hostname) | |
44 | ||
45 | i_id = self.createAndAddInterface( | |
46 | host_id, ip_address, ipv4_address=ip_address, hostname_resolution=[hostname]) | |
43 | host_id = self.createAndAddHost(ip_address, hostnames=[hostname]) | |
47 | 44 | |
48 | 45 | if re.match("HPING", output): |
49 | 46 | |
53 | 50 | service = self.srv[sport.group(1)] |
54 | 51 | |
55 | 52 | if reci.group(1) == "SA": |
56 | s_id = self.createAndAddServiceToInterface( | |
57 | host_id, i_id, service, protocol="tcp", ports=ssport, status="open") | |
53 | s_id = self.createAndAddServiceToHost( | |
54 | host_id, service, protocol="tcp", ports=ssport, status="open") | |
58 | 55 | |
59 | 56 | lineas = output.split("\n") |
60 | 57 | |
66 | 63 | port = [list[0]] |
67 | 64 | |
68 | 65 | if list[2] == "S" and list[3] == "A": |
69 | s_id = self.createAndAddServiceToInterface( | |
70 | host_id, i_id, service, protocol="tcp", ports=port, status="open") | |
66 | s_id = self.createAndAddServiceToHost( | |
67 | host_id, service, protocol="tcp", ports=port, status="open") | |
71 | 68 | |
72 | 69 | |
73 | 70 | def createPlugin(): |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | 5 | from faraday_plugins.plugins.plugin import PluginBase |
6 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
6 | 7 | import re |
8 | from collections import defaultdict | |
7 | 9 | |
8 | 10 | __author__ = "Francisco Amato" |
9 | 11 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
60 | 62 | self._temp_file_extension = "txt" |
61 | 63 | self.xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") |
62 | 64 | |
63 | def parseOutputString(self, output, debug=False): | |
65 | def parseOutputString(self, output): | |
64 | 66 | """ |
65 | 67 | This method will discard the output the shell sends, it will read it from |
66 | 68 | the xml where it expects it to be present. |
70 | 72 | """ |
71 | 73 | |
72 | 74 | parser = HydraParser(output) |
73 | ||
74 | i = 0 | |
75 | hosts = {} | |
75 | hosts = defaultdict(list) | |
76 | 76 | service = '' |
77 | 77 | port = '' |
78 | 78 | |
80 | 80 | |
81 | 81 | service = item['plugin'] |
82 | 82 | port = item['port'] |
83 | ||
84 | if item['ip'] not in hosts: | |
85 | hosts[item['ip']] = [] | |
86 | ||
87 | 83 | hosts[item['ip']].append([item['login'], item['password']]) |
88 | 84 | |
89 | 85 | for k, v in hosts.items(): |
90 | ||
91 | h_id = self.createAndAddHost(k) | |
92 | ||
93 | if self._isIPV4(k): | |
94 | ||
95 | i_id = self.createAndAddInterface( | |
96 | h_id, | |
97 | k, | |
98 | ipv4_address=k) | |
99 | ||
86 | ip = resolve_hostname(k) | |
87 | if ip != k: | |
88 | hostnames = [k] | |
100 | 89 | else: |
101 | i_id = self.createAndAddInterface( | |
102 | h_id, | |
103 | k, | |
104 | ipv6_address=k) | |
105 | ||
106 | s_id = self.createAndAddServiceToInterface( | |
90 | hostnames = None | |
91 | h_id = self.createAndAddHost(ip, hostnames=hostnames) | |
92 | s_id = self.createAndAddServiceToHost( | |
107 | 93 | h_id, |
108 | i_id, | |
109 | 94 | service, |
110 | 95 | ports=[port], |
111 | 96 | protocol="tcp", |
222 | 222 | self.framework_version = "1.0.0" |
223 | 223 | self.options = None |
224 | 224 | |
225 | def parseOutputString(self, output, debug=False): | |
225 | def parseOutputString(self, output): | |
226 | 226 | parser = ImpactXmlParser(output) |
227 | 227 | mapped_services = {} |
228 | 228 | mapped_ports = {} |
229 | 229 | for item in parser.items: |
230 | ||
231 | h_id = self.createAndAddHost( | |
232 | item.ip, | |
233 | item.os + " " + item.arch) | |
234 | ||
235 | i_id = self.createAndAddInterface( | |
236 | h_id, | |
237 | item.ip, | |
238 | ipv4_address=item.ip, | |
239 | hostname_resolution=[item.host]) | |
230 | os_string = f"{item.os} {item.arch }" | |
231 | h_id = self.createAndAddHost(item.ip, os=os_string, hostnames=[item.host]) | |
240 | 232 | |
241 | 233 | for service in item.services: |
242 | s_id = self.createAndAddServiceToInterface( | |
234 | s_id = self.createAndAddServiceToHost( | |
243 | 235 | h_id, |
244 | i_id, | |
245 | 236 | service['name'], |
246 | 237 | service['protocol'], |
247 | 238 | ports=[service['port']], |
281 | 272 | ref=v.ref) |
282 | 273 | |
283 | 274 | for p in item.ports: |
284 | s_id = self.createAndAddServiceToInterface( | |
275 | s_id = self.createAndAddServiceToHost( | |
285 | 276 | h_id, |
286 | i_id, | |
287 | 277 | p['port'], |
288 | 278 | p['protocol'], |
289 | 279 | ports=[p['port']], |
74 | 74 | self.plugin_version = "0.0.1" |
75 | 75 | self.options = None |
76 | 76 | |
77 | def parseOutputString(self, output, debug=False): | |
77 | def parseOutputString(self, output): | |
78 | 78 | |
79 | 79 | parser = Ip360Parser(output) |
80 | 80 | for host, interface, service, vulnerability in parser.parse(): |
81 | ||
82 | h_id = self.createAndAddHost(host.get("name"), host.get("os")) | |
83 | ||
84 | i_id = self.createAndAddInterface( | |
85 | h_id, | |
86 | interface.get("name"), | |
87 | ipv4_address=interface.get("name"), | |
88 | hostname_resolution=interface.get("hostname_resolution"), | |
89 | network_segment=interface.get("network_segment")) | |
90 | ||
91 | ||
81 | h_id = self.createAndAddHost(host.get("name"), host.get("os"), hostnames=interface.get("hostname_resolution")) | |
92 | 82 | if service.get("port") == "-": |
93 | 83 | port = "0" |
94 | 84 | protocol = "unknown" |
96 | 86 | port = service.get("port").split("/")[0] |
97 | 87 | protocol = service.get("port").split("/")[1] |
98 | 88 | |
99 | s_id = self.createAndAddServiceToInterface( | |
89 | s_id = self.createAndAddServiceToHost( | |
100 | 90 | h_id, |
101 | i_id, | |
102 | 91 | service.get("port"), |
103 | 92 | protocol=protocol, |
104 | 93 | ports=[port]) |
131 | 131 | self.options = None |
132 | 132 | self._current_output = None |
133 | 133 | |
134 | def parseOutputString(self, output, debug=False): | |
134 | def parseOutputString(self, output): | |
135 | 135 | |
136 | 136 | parser = JunitXmlParser(output) |
137 | 137 | for item in parser.items: |
138 | 138 | h_id = self.createAndAddHost(item.host, os="Linux") |
139 | i_id = self.createAndAddInterface(h_id, item.host, ipv4_address=item.host) | |
140 | 139 | self.createAndAddVulnToHost(h_id, name=item.name, desc=item.message, ref=[], severity="High") |
141 | 140 | del parser |
142 | 141 |
7 | 7 | import os |
8 | 8 | import zipfile |
9 | 9 | |
10 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | ||
10 | 12 | try: |
11 | 13 | import xml.etree.cElementTree as ET |
12 | 14 | import xml.etree.ElementTree as ET_ORIG |
17 | 19 | |
18 | 20 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
19 | 21 | |
20 | current_path = os.path.abspath(os.getcwd()) | |
21 | 22 | |
22 | 23 | __author__ = "Ezequiel Tavella" |
23 | 24 | __copyright__ = "Copyright (c) 2015, Infobyte LLC" |
57 | 58 | |
58 | 59 | if maltego_file_dns in mtgl_file.namelist(): |
59 | 60 | xml_dns = ET.parse(mtgl_file.open(maltego_file_dns)) |
60 | check_files.update({"DNS": xml_dns}) | |
61 | check_files.update({"domain": xml_dns}) | |
61 | 62 | |
62 | 63 | if maltego_file_domain in mtgl_file.namelist(): |
63 | 64 | xml_domain = ET.parse(mtgl_file.open(maltego_file_domain)) |
111 | 112 | def __init__(self): |
112 | 113 | self.ip = "" |
113 | 114 | self.node_id = "" |
114 | self.dns_name = "" | |
115 | self.dns_name = set() | |
115 | 116 | self.website = "" |
116 | 117 | self.netblock = "" |
117 | 118 | self.location = "" |
119 | 120 | self.ns_record = "" |
120 | 121 | |
121 | 122 | |
122 | class MaltegoMtgxParser(): | |
123 | class MaltegoParser(): | |
123 | 124 | |
124 | 125 | def __init__(self, xml_file, extension): |
125 | 126 | |
168 | 169 | entity = node.find( |
169 | 170 | "{http://graphml.graphdrawing.org/xmlns}data/" |
170 | 171 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity") |
172 | ||
173 | ||
171 | 174 | # Check if is IPv4Address |
172 | if entity.get("type") != "maltego.IPv4Address": | |
175 | if entity.get("type") not in ("maltego.IPv4Address", "maltego.Domain", "maltego.Website"): | |
173 | 176 | return None |
174 | 177 | |
175 | 178 | # Get IP value |
177 | 180 | "{http://maltego.paterva.com/xml/mtgx}Properties/" |
178 | 181 | "{http://maltego.paterva.com/xml/mtgx}Property/" |
179 | 182 | "{http://maltego.paterva.com/xml/mtgx}Value") |
180 | ||
181 | return {"node_id": node_id, "ip": value.text} | |
183 | if entity.get("type") in ("maltego.Domain", "maltego.Website"): | |
184 | ip = resolve_hostname(value.text) | |
185 | hostname = value.text | |
186 | else: | |
187 | ip = value.text | |
188 | hostname = None | |
189 | return {"node_id": node_id, "ip": ip, "hostname": hostname} | |
182 | 190 | |
183 | 191 | def getNode(self, node_id): |
184 | 192 | |
323 | 331 | host = Host() |
324 | 332 | host.ip = result.get("ip") |
325 | 333 | host.node_id = result.get("node_id") |
326 | ||
334 | if result.get("hostname"): | |
335 | host.dns_name.add(result.get("hostname")) | |
327 | 336 | # Get relations with other nodes |
328 | 337 | node_relations = self.relations[host.node_id] |
329 | 338 | |
334 | 343 | target_type = self.getType(target_node) |
335 | 344 | |
336 | 345 | # Check type of node y add data to host... |
337 | if target_type == "maltego.DNSName": | |
338 | host.dns_name = self.getValue(target_node) | |
346 | if target_type in ("maltego.DNSName", "maltego.Domain"): | |
347 | host.dns_name.add(self.getValue(target_node)['value']) | |
339 | 348 | elif target_type == "maltego.Website": |
340 | 349 | host.website = self.getWebsite(target_node) |
341 | 350 | elif target_type == "maltego.Netblock": |
377 | 386 | "Entities/maltego.Person.entity", "Entities/maltego.PhoneNumber.entity", |
378 | 387 | "Entities/maltego.Website.entity", "Entities/maltego.Hash.entity", |
379 | 388 | "Entities/maltego.hashtag.entity", "Entities/maltego.TwitterUserList.entity"} |
380 | self.current_path = None | |
381 | self.options = None | |
382 | self._current_output = None | |
383 | self._command_regex = re.compile( | |
384 | r'^(sudo maltego|maltego|\.\/maltego).*?') | |
385 | global current_path | |
386 | ||
387 | def parseOutputString(self, output, debug=False): | |
388 | ||
389 | ||
390 | def parseOutputString(self, output): | |
389 | 391 | if 'Graphs/Graph1.graphml' in output.namelist(): |
390 | maltego_parser = MaltegoMtgxParser(output, self.extension[1]) | |
391 | if not maltego_parser.parse(): | |
392 | maltego_parser = MaltegoParser(output, self.extension[1]) | |
393 | hosts = maltego_parser.parse() | |
394 | if not hosts: | |
395 | self.logger.warning("No hosts data found in maltego report") | |
392 | 396 | pass |
393 | 397 | else: |
394 | for host in maltego_parser.parse(): | |
398 | for host in hosts: | |
395 | 399 | if host.ip is None: |
396 | 400 | ip = '0.0.0.0' |
401 | self.logger.warning("Unknown IP") | |
397 | 402 | else: |
398 | 403 | ip = host.ip |
399 | host_id = self.createAndAddHost(name=ip) | |
400 | # Create interface | |
401 | try: | |
402 | network_segment = host.netblock["ipv4_range"] | |
403 | hostname_resolution = [host.dns_name["value"]] | |
404 | except TypeError: | |
405 | pass | |
406 | network_segment = "unknown" | |
407 | hostname_resolution = "unknown" | |
408 | interface_id = self.createAndAddInterface(host_id=host_id, name=ip, ipv4_address=ip, | |
409 | network_segment=network_segment, | |
410 | hostname_resolution=[hostname_resolution]) | |
411 | # Create note with NetBlock information | |
412 | if host.netblock: | |
413 | try: | |
414 | text = f'Network owner:\n {host.netblock["network_owner"]} ' \ | |
415 | f'Country:\n {host.netblock["country"]}' | |
416 | except TypeError: | |
417 | text = "unknown" | |
418 | ||
419 | self.createAndAddNoteToHost(host_id=host_id, name="Netblock Information", | |
420 | text=text.encode('ascii', 'ignore')) | |
421 | ||
422 | # Create note with host location | |
423 | if host.location: | |
424 | try: | |
425 | text = f'Location:\n {host.location["name"]} \nArea:\n {host.location["area"]} ' \ | |
426 | f'\nArea 2:\n {host.location["area_2"]} ' \ | |
427 | f'\nCountry_code:\n { host.location["country_code"]} ' \ | |
428 | f'\nLatitude:\n {host.location["latitude"]} \nLongitude:\n {host.location["longitude"]}' | |
429 | except TypeError: | |
430 | text = "unknown" | |
431 | ||
432 | self.createAndAddNoteToHost(host_id=host_id, name="Location Information", | |
433 | text=text.encode('ascii', 'ignore')) | |
434 | ||
435 | # Create service web server | |
436 | if host.website: | |
437 | try: | |
438 | description = f'SSL Enabled: {host.website["ssl_enabled"]}' | |
439 | except TypeError: | |
440 | description = "unknown" | |
441 | ||
442 | service_id = self.createAndAddServiceToInterface(host_id=host_id, interface_id=interface_id, | |
443 | name=host.website["name"], protocol="TCP:HTTP", | |
444 | ports=[80], description=description) | |
445 | ||
446 | try: | |
447 | text = f'Urls: \n {host.website["urls"]}' | |
448 | self.createAndAddNoteToService(host_id=host_id, service_id=service_id, name="URLs", | |
449 | text=text.encode('ascii', 'ignore')) | |
450 | except TypeError: | |
451 | pass | |
452 | ||
453 | if host.mx_record: | |
454 | self.createAndAddServiceToInterface(host_id=host_id, interface_id=interface_id, | |
455 | name=host.mx_record["value"], protocol="SMTP", ports=[25], | |
456 | description="E-mail Server") | |
457 | ||
458 | if host.ns_record: | |
459 | self.createAndAddServiceToInterface(host_id=host_id, interface_id=interface_id, | |
460 | name=host.ns_record["value"], protocol="DNS", ports=[53], | |
461 | description="DNS Server") | |
404 | host_id = self.createAndAddHost(ip, hostnames=list(host.dns_name)) | |
405 | # Create note with NetBlock information | |
406 | if host.netblock: | |
407 | try: | |
408 | text = f'Network owner:\n {host.netblock["network_owner"]} ' \ | |
409 | f'Country:\n {host.netblock["country"]}' | |
410 | except TypeError: | |
411 | text = "unknown" | |
412 | ||
413 | self.createAndAddNoteToHost(host_id=host_id, name="Netblock Information", | |
414 | text=text.encode('ascii', 'ignore')) | |
415 | ||
416 | # Create note with host location | |
417 | if host.location: | |
418 | try: | |
419 | text = f'Location:\n {host.location["name"]} \nArea:\n {host.location["area"]} ' \ | |
420 | f'\nArea 2:\n {host.location["area_2"]} ' \ | |
421 | f'\nCountry_code:\n { host.location["country_code"]} ' \ | |
422 | f'\nLatitude:\n {host.location["latitude"]} \nLongitude:\n {host.location["longitude"]}' | |
423 | except TypeError: | |
424 | text = "unknown" | |
425 | ||
426 | self.createAndAddNoteToHost(host_id=host_id, name="Location Information", | |
427 | text=text.encode('ascii', 'ignore')) | |
428 | ||
429 | # Create service web server | |
430 | if host.website: | |
431 | try: | |
432 | description = f'SSL Enabled: {host.website["ssl_enabled"]}' | |
433 | except TypeError: | |
434 | description = "unknown" | |
435 | ||
436 | service_id = self.createAndAddServiceToHost(host_id=host_id, name=host.website["name"], | |
437 | protocol="TCP:HTTP", ports=[80], | |
438 | description=description) | |
439 | ||
440 | try: | |
441 | text = f'Urls: \n {host.website["urls"]}' | |
442 | self.createAndAddNoteToService(host_id=host_id, service_id=service_id, name="URLs", | |
443 | text=text.encode('ascii', 'ignore')) | |
444 | except TypeError: | |
445 | pass | |
446 | ||
447 | if host.mx_record: | |
448 | self.createAndAddServiceToHost(host_id=host_id, name=host.mx_record["value"], protocol="SMTP", | |
449 | ports=[25], description="E-mail Server") | |
450 | ||
451 | if host.ns_record: | |
452 | self.createAndAddServiceToHost(host_id=host_id, name=host.ns_record["value"], protocol="DNS", | |
453 | ports=[53], description="DNS Server") | |
462 | 454 | else: |
463 | maltego_parser = MaltegoMtgxParser(output, self.extension[0]) | |
455 | maltego_parser = MaltegoParser(output, self.extension[0]) | |
456 | if not maltego_parser.xml.get('domain') or not maltego_parser.xml.get('ipv4'): | |
457 | return | |
458 | if maltego_parser.xml.get('domain'): | |
459 | hostnames = maltego_parser.getInfoMtgl(maltego_parser.xml['domain'], 'fqdn') | |
460 | else: | |
461 | hostnames = None | |
464 | 462 | if maltego_parser.xml.get('ipv4'): |
465 | 463 | host_ip = maltego_parser.getInfoMtgl(maltego_parser.xml['ipv4'], 'ipv4-address') |
466 | host_id = self.createAndAddHost(name=host_ip) | |
464 | host_id = self.createAndAddHost(name=host_ip, hostnames=hostnames) | |
467 | 465 | else: |
468 | host_id = self.createAndAddHost(name=self.name) | |
469 | 466 | host_ip = '0.0.0.0' |
470 | ||
471 | if maltego_parser.xml.get('DNS'): | |
472 | hostname_resolution = maltego_parser.getInfoMtgl(maltego_parser.xml['DNS'], 'fqdn') | |
473 | interface_id = self.createAndAddInterface(host_id=host_id, name=host_ip, ipv4_address=host_ip, | |
474 | hostname_resolution=[hostname_resolution]) | |
475 | else: | |
476 | interface_id = self.createAndAddInterface(host_id=host_id, name=host_ip, ipv4_address=host_ip) | |
467 | host_id = self.createAndAddHost(name=host_ip, hostnames=hostnames) | |
468 | ||
477 | 469 | |
478 | 470 | if maltego_parser.xml.get('location'): |
479 | 471 | location_name = maltego_parser.getInfoMtgl(maltego_parser.xml['location'], 'location.name') |
499 | 491 | web_ssh = maltego_parser.getInfoMtgl(maltego_parser.xml['web'], 'website.ssl-enabled') |
500 | 492 | description = f'SSL Enabled: {web_ssh}' |
501 | 493 | |
502 | service_id = self.createAndAddServiceToInterface(host_id=host_id, interface_id=interface_id, | |
503 | name=web_name, protocol="TCP:HTTP", ports=web_port, | |
504 | description=description) | |
494 | service_id = self.createAndAddServiceToHost(host_id=host_id, name=web_name, protocol="TCP:HTTP", | |
495 | ports=web_port, description=description) | |
505 | 496 | |
506 | 497 | self.createAndAddNoteToService(host_id=host_id, service_id=service_id, name="URLs", |
507 | 498 | text=text.encode('ascii', 'ignore')) |
509 | 500 | if maltego_parser.xml.get('mxrecord'): |
510 | 501 | mx_name = maltego_parser.getInfoMtgl(maltego_parser.xml['mxrecord'], 'fqdn') |
511 | 502 | |
512 | self.createAndAddServiceToInterface(host_id=host_id, interface_id=interface_id, name=mx_name, | |
513 | protocol="SMTP", ports=[25], description="E-mail Server") | |
503 | self.createAndAddServiceToHost(host_id=host_id, name=mx_name, protocol="SMTP", ports=[25], | |
504 | description="E-mail Server") | |
514 | 505 | |
515 | 506 | if maltego_parser.xml.get('nsrecord'): |
516 | 507 | ns_name = maltego_parser.getInfoMtgl(maltego_parser.xml['nsrecord'], 'fqdn') |
517 | self.createAndAddServiceToInterface(host_id=host_id, interface_id=interface_id, name=ns_name, | |
518 | protocol="DNS", ports=[53], description="DNS Server") | |
508 | self.createAndAddServiceToHost(host_id=host_id, name=ns_name, protocol="DNS", ports=[53], | |
509 | description="DNS Server") | |
519 | 510 | |
520 | 511 | |
521 | 512 |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | from faraday_plugins.plugins.plugin import PluginByExtension | |
6 | import re | |
7 | from datetime import datetime | |
8 | ||
9 | __author__ = "Blas Moyano" | |
10 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
11 | __credits__ = ["Blas Moyano"] | |
12 | __license__ = "" | |
13 | __version__ = "1.0.0" | |
14 | __maintainer__ = "Blas Moyano" | |
15 | __status__ = "Development" | |
16 | ||
17 | ||
18 | class MbsaParser: | |
19 | def __init__(self, log_output): | |
20 | self.computer_name = re.search('(Computer name:) (.*[A-Z])', log_output) | |
21 | self.ip = re.search('(IP address:) ([0-9]+(?:\.[0-9]+){3})', log_output) | |
22 | self.scan_date = re.search('(Scan date:) (.*[0-9])', log_output) | |
23 | self.issues = re.findall(r'Issue: .*', log_output) | |
24 | self.score = re.findall(r'Score: .*', log_output) | |
25 | self.result = re.findall(r'Result: .*', log_output) | |
26 | ||
27 | ||
28 | class MbsaPlugin(PluginByExtension): | |
29 | def __init__(self): | |
30 | super().__init__() | |
31 | self.id = "MBSA" | |
32 | self.name = "Microsoft Baseline Security Analyzer" | |
33 | self.plugin_version = "1.0.1" | |
34 | self.version = "MBSA 1.0" | |
35 | self.framework_version = "1.0.0" | |
36 | self.extension = ".log" | |
37 | ||
38 | def parseOutputString(self, output): | |
39 | parser = MbsaParser(output) | |
40 | detail = '' | |
41 | i = 0 | |
42 | issues_top = len(parser.issues) | |
43 | ip = '0.0.0.0' | |
44 | hostname = [] | |
45 | run_date = None | |
46 | ||
47 | if parser.ip is not None: | |
48 | ip = parser.ip.group(2) | |
49 | if parser.computer_name is not None: | |
50 | hostname.append(parser.computer_name.group(2)) | |
51 | if parser.scan_date is not None: | |
52 | run_date = datetime.strptime(parser.scan_date.group(2), '%Y/%m/%d %H:%M') | |
53 | ||
54 | host_id = self.createAndAddHost( | |
55 | ip, | |
56 | 'Windows', | |
57 | hostnames=hostname) | |
58 | ||
59 | for issue in parser.issues: | |
60 | ||
61 | test = re.search(parser.issues[i], output) | |
62 | ||
63 | if i+1 != issues_top: | |
64 | test_issue = re.search(parser.issues[i+1], output) | |
65 | else: | |
66 | end = None | |
67 | try: | |
68 | start = test.end() | |
69 | end = test_issue.start() | |
70 | except: | |
71 | start = None | |
72 | ||
73 | if start is not None: | |
74 | if end is None: | |
75 | result_info = output[start:] | |
76 | else: | |
77 | result_info = output[start:end] | |
78 | result_info.rstrip('\n') | |
79 | result_info = result_info.replace(parser.score[i], '') | |
80 | result_info = result_info.replace(parser.result[i], '') | |
81 | result_info = result_info.strip() | |
82 | if result_info: | |
83 | detail = re.search('(Detail:)', result_info) | |
84 | if not None: | |
85 | detail = result_info | |
86 | result_info = parser.result[i] | |
87 | ||
88 | else: | |
89 | detail = '' | |
90 | result_info = parser.result[i] | |
91 | score = parser.score[i].replace('Score: ', '').strip() | |
92 | if score != 'Check passed': | |
93 | if score == 'Best practice' or score == 'Unable to scan': | |
94 | severity = "info" | |
95 | elif score == 'Check failed (non-critical)': | |
96 | severity = 'med' | |
97 | elif score == 'Check failed': | |
98 | severity = 'high' | |
99 | else: | |
100 | severity = 'info' | |
101 | ||
102 | self.createAndAddVulnToHost( | |
103 | host_id, | |
104 | issue.replace('Issue: ', '').strip(), | |
105 | desc=result_info.replace('Result: ', '').strip(), | |
106 | ref=None, | |
107 | severity=severity, | |
108 | data=detail, | |
109 | run_date=run_date | |
110 | ) | |
111 | ||
112 | i += 1 | |
113 | ||
114 | ||
115 | def createPlugin(): | |
116 | return MbsaPlugin() |
84 | 84 | |
85 | 85 | for item in parser.items: |
86 | 86 | |
87 | h_id = self.createAndAddHost(item['ip']) | |
88 | if self._isIPV4(item['ip']): | |
89 | i_id = self.createAndAddInterface( | |
90 | h_id, | |
91 | item['ip'], | |
92 | ipv4_address=item['ip'], | |
93 | hostname_resolution=[item['host']]) | |
94 | else: | |
95 | i_id = self.createAndAddInterface( | |
96 | h_id, | |
97 | item['ip'], | |
98 | ipv6_address=item['ip'], | |
99 | hostname_resolution=[item['host']]) | |
87 | h_id = self.createAndAddHost(item['ip'], hostnames=[item['host']]) | |
100 | 88 | |
101 | 89 | port = self.port if self.port else item['port'] |
102 | 90 | |
103 | s_id = self.createAndAddServiceToInterface( | |
91 | s_id = self.createAndAddServiceToHost( | |
104 | 92 | h_id, |
105 | i_id, | |
106 | 93 | item['service'], |
107 | 94 | ports=[port], |
108 | 95 | protocol="tcp", |
336 | 336 | self.target = None |
337 | 337 | |
338 | 338 | |
339 | def parseOutputString(self, output, debug=False): | |
339 | def parseOutputString(self, output): | |
340 | 340 | """ |
341 | 341 | This method will discard the output the shell sends, it will read it from |
342 | 342 | the xml where it expects it to be present. |
343 | ||
344 | NOTE: if 'debug' is true then it is being run from a test case and the | |
345 | output being sent is valid. | |
346 | 343 | """ |
347 | 344 | |
348 | 345 | parser = MetasploitXmlParser(output) |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | ||
7 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
8 | try: | |
9 | import xml.etree.cElementTree as ET | |
10 | except ImportError: | |
11 | import xml.etree.ElementTree as ET | |
12 | ||
13 | ||
14 | __author__ = "Blas Moyano" | |
15 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
16 | __credits__ = ["Blas Moyano"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0" | |
19 | __maintainer__ = "Blas Moyano" | |
20 | __status__ = "Development" | |
21 | ||
22 | ||
23 | class NcrackParser: | |
24 | def __init__(self, xml_output): | |
25 | self.tree = self.parse_xml(xml_output) | |
26 | if self.tree: | |
27 | scanner = self.tree.attrib.get('scanner', None) | |
28 | args = self.tree.attrib.get('args', None) | |
29 | start = self.tree.attrib.get('start', None) | |
30 | start_str = self.tree.attrib.get('start_str', None) | |
31 | service_data = None if self.tree.find('service') is None else \ | |
32 | self.get_service(self.tree.findall('service')) | |
33 | self.ncrack_info = { | |
34 | "scanner_name": scanner, | |
35 | "args": args, | |
36 | "date": start, | |
37 | "date_str": start_str, | |
38 | "info_service": service_data | |
39 | } | |
40 | else: | |
41 | self.tree = None | |
42 | ||
43 | def parse_xml(self, xml_output): | |
44 | try: | |
45 | tree = ET.fromstring(xml_output) | |
46 | except SyntaxError as err: | |
47 | print('SyntaxError In xml: %s. %s' % (err, xml_output)) | |
48 | return None | |
49 | return tree | |
50 | ||
51 | def get_service(self, tree): | |
52 | list_service_info = [] | |
53 | for service in tree: | |
54 | address = service.find('address') | |
55 | port = service.find('port') | |
56 | credential = service.find('credentials') | |
57 | if address is not None: | |
58 | addr = address.attrib.get('addr', None) | |
59 | addr_type = address.attrib.get('addrtype', None) | |
60 | else: | |
61 | addr = None | |
62 | addr_type = None | |
63 | ||
64 | if port is not None: | |
65 | protocol = port.attrib.get('protocol', None) | |
66 | port_number = port.attrib.get('portid', None) | |
67 | port_name = port.attrib.get('name', None) | |
68 | else: | |
69 | protocol = None | |
70 | port_number = None | |
71 | port_name = None | |
72 | ||
73 | if credential is not None: | |
74 | user = credential.attrib.get('username', None) | |
75 | passw = credential.attrib.get('password', None) | |
76 | else: | |
77 | user = None | |
78 | passw = None | |
79 | ||
80 | service_info = { | |
81 | "addr": addr, | |
82 | "addr_type": addr_type, | |
83 | "protocol": protocol, | |
84 | "port_number": port_number, | |
85 | "port_name": port_name, | |
86 | "user": user, | |
87 | "passw": passw | |
88 | } | |
89 | list_service_info.append(service_info) | |
90 | return list_service_info | |
91 | ||
92 | ||
93 | class NcrackPlugin(PluginXMLFormat): | |
94 | def __init__(self): | |
95 | super().__init__() | |
96 | self.identifier_tag = "ncrackrun" | |
97 | self.id = 'ncrack' | |
98 | self.name = 'ncrack XML Plugin' | |
99 | self.plugin_version = '0.0.1' | |
100 | self.version = '1.0.0' | |
101 | self.framework_version = '1.0.0' | |
102 | ||
103 | def parseOutputString(self, output): | |
104 | parser = NcrackParser(output) | |
105 | data = parser.ncrack_info | |
106 | ||
107 | for service_vuln in data['info_service']: | |
108 | host_id = self.createAndAddHost(service_vuln['addr'], | |
109 | description=f"{data['scanner_name']} - args: {data['args']}") | |
110 | ||
111 | service_id = self.createAndAddServiceToHost(host_id, | |
112 | service_vuln['addr'], | |
113 | ports=service_vuln['port_number'], | |
114 | protocol=service_vuln['protocol'], | |
115 | description=service_vuln['port_name']) | |
116 | if service_vuln['user'] is not None or service_vuln['passw'] is not None: | |
117 | self.createAndAddCredToService(host_id, | |
118 | service_id, | |
119 | username=service_vuln['user'], | |
120 | password=service_vuln['passw']) | |
121 | ||
122 | ||
123 | def createPlugin(): | |
124 | return NcrackPlugin() |
116 | 116 | self.version = "1.0.0" |
117 | 117 | self._command_regex = re.compile(r'^(sudo ndiff|ndiff)\s+.*?') |
118 | 118 | |
119 | def parseOutputString(self, output, debug=False): | |
119 | def parseOutputString(self, output): | |
120 | 120 | parser = NdiffXmlParser(output) |
121 | 121 | for host in parser.hostDiff: |
122 | 122 | if host.ip is None: |
4 | 4 | |
5 | 5 | """ |
6 | 6 | import dateutil |
7 | from collections import namedtuple | |
7 | 8 | |
8 | 9 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
9 | 10 | import xml.etree.ElementTree as ET |
11 | 12 | |
12 | 13 | __author__ = "Blas" |
13 | 14 | __copyright__ = "Copyright (c) 2019, Infobyte LLC" |
14 | __credits__ = ["Blas"] | |
15 | __credits__ = ["Blas", "Nicolas Rebagliati"] | |
15 | 16 | __license__ = "" |
16 | 17 | __version__ = "1.0.0" |
17 | 18 | __maintainer__ = "Blas" |
18 | 19 | __email__ = "[email protected]" |
19 | 20 | __status__ = "Development" |
20 | 21 | |
22 | ReportItem = namedtuple('ReportItem', ['port', 'svc_name', 'protocol', 'severity', 'plugin_id', | |
23 | 'plugin_name', 'plugin_family', 'description', 'plugin_output', 'info']) | |
21 | 24 | |
22 | 25 | class NessusParser: |
23 | 26 | """ |
44 | 47 | |
45 | 48 | def getPolicy(self, tree): |
46 | 49 | policy_tree = tree.find('Policy') |
47 | return Policy(policy_tree) | |
50 | if policy_tree: | |
51 | return Policy(policy_tree) | |
52 | else: | |
53 | return None | |
48 | 54 | |
49 | 55 | def getReport(self, tree): |
50 | 56 | report_tree = tree.find('Report') |
51 | 57 | return Report(report_tree) |
52 | 58 | |
59 | def parse_compliance_data(self, data: dict): | |
60 | compliance_data = {} | |
61 | for key, value in data.items(): | |
62 | if 'compliance-' in key: | |
63 | compliance_name = key.split("}")[-1] | |
64 | compliance_data[compliance_name] = value | |
65 | return compliance_data | |
53 | 66 | |
54 | 67 | class Policy(): |
55 | 68 | def __init__(self, policy_node): |
97 | 110 | plugin_status)) |
98 | 111 | return item_plugin |
99 | 112 | |
100 | ||
101 | 113 | class Report(): |
102 | 114 | def __init__(self, report_node): |
103 | 115 | self.node = report_node |
109 | 121 | self.report_json = {} |
110 | 122 | if self.report_host is not None: |
111 | 123 | for x in self.node: |
112 | self.report_host_ip = x.attrib.get('name') | |
113 | self.host_properties = self.gethosttag(x.find('HostProperties')) | |
114 | self.report_item = self.getreportitems(x.findall('ReportItem')) | |
115 | self.report_ip.append(self.report_host_ip) | |
116 | self.report_desc.append(self.host_properties) | |
117 | self.report_serv.append(self.report_item) | |
124 | report_host_ip = x.attrib.get('name') | |
125 | host_properties = self.gethosttag(x.find('HostProperties')) | |
126 | report_items = self.getreportitems(x.findall('ReportItem')) | |
127 | self.report_ip.append(report_host_ip) | |
128 | self.report_desc.append(host_properties) | |
129 | self.report_serv.append(report_items) | |
118 | 130 | self.report_json['ip'] = self.report_ip |
119 | 131 | self.report_json['desc'] = self.report_desc |
120 | 132 | self.report_json['serv'] = self.report_serv |
121 | self.report_json['host_end'] = self.host_properties.get('HOST_END') | |
133 | self.report_json['host_end'] = host_properties.get('HOST_END') | |
122 | 134 | |
123 | 135 | else: |
124 | 136 | self.report_host_ip = None |
125 | 137 | self.host_properties = None |
126 | self.report_item = None | |
138 | self.report_items = None | |
127 | 139 | self.report_json = None |
128 | 140 | |
129 | 141 | def getreportitems(self, items): |
130 | result_item = [] | |
142 | report_items = [] | |
131 | 143 | |
132 | 144 | for item in items: |
133 | self.port = item.attrib.get('port') | |
134 | self.svc_name = item.attrib.get('svc_name') | |
135 | self.protocol = item.attrib.get('protocol') | |
136 | self.severity = item.attrib.get('severity') | |
137 | self.plugin_id = item.attrib.get('pluginID') | |
138 | self.plugin_name = item.attrib.get('pluginName') | |
139 | self.plugin_family = item.attrib.get('pluginFamily') | |
145 | port = item.attrib.get('port') | |
146 | svc_name = item.attrib.get('svc_name') | |
147 | protocol = item.attrib.get('protocol') | |
148 | severity = item.attrib.get('severity') | |
149 | plugin_id = item.attrib.get('pluginID') | |
150 | plugin_name = item.attrib.get('pluginName') | |
151 | plugin_family = item.attrib.get('pluginFamily') | |
140 | 152 | if item.find('plugin_output') is not None: |
141 | self.plugin_output = item.find('plugin_output').text | |
153 | plugin_output = item.find('plugin_output').text | |
142 | 154 | else: |
143 | self.plugin_output = "Not Description" | |
155 | plugin_output = "Not Description" | |
144 | 156 | if item.find('description') is not None: |
145 | self.description = item.find('description').text | |
157 | description = item.find('description').text | |
146 | 158 | else: |
147 | self.description = "Not Description" | |
148 | ||
149 | self.info = self.getinfoitem(item) | |
150 | result_item.append((self.port, self.svc_name, self.protocol, self.severity, self.plugin_id, | |
151 | self.plugin_name, self.plugin_family, self.description, self.plugin_output, self.info)) | |
152 | return result_item | |
159 | description = "Not Description" | |
160 | info = self.getinfoitem(item) | |
161 | report_items.append(ReportItem(*[port, svc_name, protocol, severity, plugin_id, | |
162 | plugin_name, plugin_family, description, plugin_output, info])) | |
163 | return report_items | |
153 | 164 | |
154 | 165 | def getinfoitem(self, item): |
155 | 166 | item_tags = {} |
162 | 173 | for t in tags: |
163 | 174 | host_tags.setdefault(t.attrib.get('name'), t.text) |
164 | 175 | return host_tags |
165 | ||
166 | 176 | |
167 | 177 | class NessusPlugin(PluginXMLFormat): |
168 | 178 | """ |
190 | 200 | """ |
191 | 201 | try: |
192 | 202 | parser = NessusParser(output) |
193 | except: | |
203 | except Exception as e: | |
204 | self.logger.error(str(e)) | |
194 | 205 | return None |
195 | 206 | |
196 | 207 | if parser.report.report_json is not None: |
198 | 209 | if run_date: |
199 | 210 | run_date = dateutil.parser.parse(run_date) |
200 | 211 | for set_info, ip in enumerate(parser.report.report_json['ip'], start=1): |
201 | if 'mac-address' in parser.report.report_json['desc'][set_info - 1]: | |
202 | mac = parser.report.report_json['desc'][set_info - 1]['mac-address'] | |
203 | else: | |
204 | mac = '' | |
205 | if 'operating-system' in parser.report.report_json['desc'][set_info - 1]: | |
206 | os = parser.report.report_json['desc'][set_info - 1]['operating-system'] | |
207 | else: | |
208 | os = None | |
209 | ||
210 | if 'host-ip' in parser.report.report_json['desc'][set_info - 1]: | |
211 | ip_host = parser.report.report_json['desc'][set_info - 1]['host-ip'] | |
212 | else: | |
213 | ip_host = "0.0.0.0" | |
214 | if 'host-fqdn' in parser.report.report_json['desc'][set_info - 1]: | |
215 | website = parser.report.report_json['desc'][set_info - 1]['host-fqdn'] | |
216 | host_name = [] | |
217 | host_name.append(parser.report.report_json['desc'][set_info - 1]['host-fqdn']) | |
218 | else: | |
219 | website = None | |
220 | host_name = None | |
221 | ||
212 | website = None | |
213 | mac = parser.report.report_json['desc'][set_info - 1].get('mac-address', '') | |
214 | os = parser.report.report_json['desc'][set_info - 1].get('operating-system', None) | |
215 | ip_host = parser.report.report_json['desc'][set_info - 1].get('host-ip', ip) | |
216 | host_name = parser.report.report_json['desc'][set_info - 1].get('host-fqdn', None) | |
217 | if host_name: | |
218 | website = host_name | |
222 | 219 | host_id = self.createAndAddHost(ip_host, os=os, hostnames=host_name, mac=mac) |
223 | 220 | |
224 | interface_id = self.createAndAddInterface(host_id, ip, ipv6_address=ip, mac=mac) | |
225 | cve = [] | |
226 | for serv in parser.report.report_json['serv'][set_info -1]: | |
227 | serv_name = serv[1] | |
228 | serv_port = serv[0] | |
229 | serv_protocol = serv[2] | |
230 | serv_status = serv[3] | |
231 | external_id = serv[4] | |
232 | serv_description = serv[7] | |
233 | cve.append(serv[8]) | |
234 | severity = serv[3] | |
235 | desc = serv[8] | |
236 | ||
237 | if serv_name == 'general': | |
238 | ref = [] | |
239 | vulnerability_name = serv[5] | |
240 | data = serv[9] | |
241 | if not data: | |
242 | continue | |
243 | if 'description' in data: | |
244 | desc = data['description'] | |
245 | else: | |
246 | desc = "No description" | |
247 | if 'solution' in data: | |
248 | resolution = data['solution'] | |
249 | else: | |
250 | resolution = "No Solution" | |
251 | if 'plugin_output' in data: | |
252 | data_po = data['plugin_output'] | |
253 | else: | |
254 | data_po = "Not data" | |
255 | ||
256 | risk_factor = "unclassified" | |
257 | if 'risk_factor' in data: | |
258 | risk_factor = data['risk_factor'] | |
259 | if risk_factor == 'None': | |
260 | risk_factor = "info" # I checked several external id and most of them were info | |
261 | ||
221 | for report_item in parser.report.report_json['serv'][set_info -1]: | |
222 | vulnerability_name = report_item.plugin_name | |
223 | if not vulnerability_name: | |
224 | continue | |
225 | item_name = report_item.svc_name | |
226 | item_port = report_item.port | |
227 | item_protocol = report_item.protocol | |
228 | item_severity = report_item.severity | |
229 | external_id = report_item.plugin_id | |
230 | serv_description = report_item.description | |
231 | #cve.append(report_item.plugin_output) | |
232 | description = report_item.plugin_output | |
233 | data = report_item.info | |
234 | risk_factor = data.get('risk_factor', None) | |
235 | cve = [] | |
236 | ref = [] | |
237 | if risk_factor == 'None' or risk_factor is None: | |
238 | risk_factor = item_severity # I checked several external id and most of them were info | |
239 | if item_name == 'general': | |
240 | description = data.get('description', '') | |
241 | resolution = data.get('solution', '') | |
242 | data_pluin_ouput = data.get('plugin_output', '') | |
262 | 243 | if 'cvss_base_score' in data: |
263 | cvss_base_score = "CVSS :{}".format(data['cvss_base_score']) | |
244 | cvss_base_score = f"CVSS:{data['cvss_base_score']}" | |
264 | 245 | ref.append(cvss_base_score) |
265 | else: | |
266 | ref = [] | |
267 | ||
268 | 246 | policyviolations = [] |
269 | if serv[6] == 'Policy Compliance': | |
247 | if report_item.plugin_family == 'Policy Compliance': | |
270 | 248 | # This condition was added to support CIS Benchmark in policy violation field. |
271 | risk_factor = 'info' | |
272 | bis_benchmark_data = serv[7].split('\n') | |
273 | policy_item = bis_benchmark_data[0] | |
274 | ||
249 | bis_benchmark_data = report_item.description.split('\n') | |
250 | compliance_data = parser.parse_compliance_data(data) | |
251 | compliance_info = compliance_data.get('compliance-info', '') | |
252 | if compliance_info and not description: | |
253 | description = compliance_info | |
254 | compliance_reference = compliance_data.get('compliance-reference', '').replace('|', ':').split(',') | |
255 | compliance_result = compliance_data.get('compliance-result', '') | |
256 | for reference in compliance_reference: | |
257 | ref.append(reference) | |
258 | compliance_check_name = compliance_data.get('compliance-check-name', '') | |
259 | compliance_solution = compliance_data.get('compliance-solution', '') | |
260 | if compliance_solution and not resolution: | |
261 | resolution = compliance_solution | |
262 | policy_item = f'{compliance_check_name} - {compliance_result}' | |
275 | 263 | for policy_check_data in bis_benchmark_data: |
276 | 264 | if 'ref.' in policy_check_data: |
277 | 265 | ref.append(policy_check_data) |
278 | ||
279 | if 'FAILED' in policy_item: | |
280 | risk_factor = 'high' | |
281 | policyviolations.append(policy_item) | |
282 | ||
283 | vulnerability_name = f'{serv[6]} {vulnerability_name} {policy_item}' | |
284 | ||
266 | if 'compliance-see-also' in compliance_data: | |
267 | ref.append(compliance_data.get('compliance-see-also')) | |
268 | # We used this info from tenable: https://community.tenable.com/s/article/Compliance-checks-in-SecurityCenter | |
269 | policyviolations.append(policy_item) | |
270 | vulnerability_name = f'{vulnerability_name}: {policy_item}' | |
285 | 271 | self.createAndAddVulnToHost(host_id, |
286 | 272 | vulnerability_name, |
287 | desc=desc, | |
273 | desc=description, | |
288 | 274 | severity=risk_factor, |
289 | 275 | resolution=resolution, |
290 | data=data_po, | |
276 | data=data_pluin_ouput, | |
291 | 277 | ref=ref, |
292 | 278 | policyviolations=policyviolations, |
293 | 279 | external_id=external_id, |
294 | 280 | run_date=run_date) |
295 | 281 | else: |
296 | data = serv[9] | |
297 | if not data: | |
298 | continue | |
299 | ref = [] | |
300 | vulnerability_name = serv[5] | |
301 | if 'description' in data: | |
302 | desc = data['description'] | |
303 | else: | |
304 | desc = "No description" | |
305 | if 'solution' in data: | |
306 | resolution = data['solution'] | |
307 | else: | |
308 | resolution = "No Solution" | |
309 | if 'plugin_output' in data: | |
310 | data_po = data['plugin_output'] | |
311 | else: | |
312 | data_po = "Not data" | |
313 | ||
314 | risk_factor = "info" | |
315 | if 'risk_factor' in data: | |
316 | risk_factor = data['risk_factor'] | |
317 | ||
318 | if risk_factor == 'None': | |
319 | risk_factor = 'info' | |
320 | ||
282 | vulnerability_name = report_item.plugin_name | |
283 | description = data.get('description', '') | |
284 | resolution = data.get('solution', '') | |
285 | data_pluin_ouput = data.get('plugin_output', '') | |
321 | 286 | if 'cvss_base_score' in data: |
322 | 287 | cvss_base_score = f"CVSS:{data['cvss_base_score']}" |
323 | 288 | ref.append(cvss_base_score) |
331 | 296 | if 'xref' in data: |
332 | 297 | ref.append(data['xref']) |
333 | 298 | |
334 | service_id = self.createAndAddServiceToInterface(host_id, | |
335 | interface_id, | |
336 | name=serv_name, | |
337 | protocol=serv_protocol, | |
338 | ports=serv_port) | |
339 | ||
340 | if serv_name == 'www' or serv_name == 'http': | |
299 | service_id = self.createAndAddServiceToHost(host_id, name=item_name, protocol=item_protocol, | |
300 | ports=item_port) | |
301 | ||
302 | if item_name == 'www' or item_name == 'http': | |
341 | 303 | self.createAndAddVulnWebToService(host_id, |
342 | 304 | service_id, |
343 | 305 | name=vulnerability_name, |
344 | desc=desc, | |
345 | data=data_po, | |
306 | desc=description, | |
307 | data=data_pluin_ouput, | |
346 | 308 | severity=risk_factor, |
347 | 309 | resolution=resolution, |
348 | 310 | ref=ref, |
354 | 316 | service_id, |
355 | 317 | name=vulnerability_name, |
356 | 318 | severity=risk_factor, |
357 | desc=desc, | |
319 | desc=description, | |
358 | 320 | ref=ref, |
359 | data=data_po, | |
321 | data=data_pluin_ouput, | |
360 | 322 | external_id=external_id, |
361 | 323 | resolution=resolution, |
362 | 324 | run_date=run_date) |
363 | else: | |
364 | ip = '0.0.0.0' | |
365 | host_id = self.createAndAddHost(ip, hostnames="Not Information") | |
366 | interface_id = self.createAndAddInterface(host_id, ip) | |
367 | service_id = self.createAndAddServiceToInterface(host_id, interface_id, name="Not Information") | |
368 | self.createAndAddVulnToService(host_id, | |
369 | service_id, | |
370 | name=parser.policy.policy_name, | |
371 | desc="No Description") | |
372 | 325 | |
373 | 326 | |
374 | 327 | def createPlugin(): |
33 | 33 | ip_address = stdout[0] |
34 | 34 | mac = stdout[2] |
35 | 35 | hostname = stdout[6].strip() |
36 | ||
37 | h_id = self.createAndAddHost(ip_address) | |
38 | self.createAndAddInterface(h_id, ip_address, ipv4_address=ip_address, mac=mac, hostname_resolution=[hostname]) | |
39 | ||
36 | h_id = self.createAndAddHost(ip_address, hostnames=[hostname]) | |
40 | 37 | return True |
41 | 38 | |
42 | 39 |
61 | 61 | try: |
62 | 62 | tree = ET.fromstring(xml_output) |
63 | 63 | except SyntaxError as err: |
64 | self.devlog("SyntaxError: %s. %s" % (err, xml_output)) | |
64 | self.logger.error("SyntaxError: %s. %s" % (err, xml_output)) | |
65 | 65 | return None |
66 | 66 | |
67 | 67 | return tree |
109 | 109 | self.port = host.group(11) |
110 | 110 | |
111 | 111 | self.name = self.get_text_from_subnode("type") |
112 | self.name_title = self.get_text_from_subnode("title") | |
112 | 113 | self.desc = self.get_text_from_subnode("description") |
113 | 114 | self.severity = self.re_map_severity(self.get_text_from_subnode("severity")) |
114 | 115 | self.certainty = self.get_text_from_subnode("certainty") |
215 | 216 | desc = BeautifulSoup(i.desc, "lxml").text |
216 | 217 | else: |
217 | 218 | desc = "" |
218 | ||
219 | v_id = self.createAndAddVulnWebToService(h_id, s_id, i.name, ref=i.ref, website=i.hostname, | |
219 | if i.name_title is None: | |
220 | name = i.name | |
221 | else: | |
222 | name = i.name_title | |
223 | v_id = self.createAndAddVulnWebToService(h_id, s_id, name, ref=i.ref, website=i.hostname, | |
220 | 224 | severity=i.severity, desc=desc, path=i.url, method=i.method, |
221 | 225 | request=i.request, response=i.response, resolution=resolution, |
222 | 226 | pname=i.param, data=i.data) |
70 | 70 | try: |
71 | 71 | tree = ET.fromstring(xml_output) |
72 | 72 | except SyntaxError as err: |
73 | self.devlog("SyntaxError: %s. %s" % (err, xml_output)) | |
73 | self.logger.error("SyntaxError: %s. %s" % (err, xml_output)) | |
74 | 74 | return None |
75 | 75 | return tree |
76 | 76 | |
189 | 189 | self.framework_version = "1.0.0" |
190 | 190 | self.options = None |
191 | 191 | |
192 | def parseOutputString(self, output, debug=False): | |
192 | def parseOutputString(self, output): | |
193 | 193 | parser = NetsparkerCloudXmlParser(output) |
194 | 194 | first = True |
195 | 195 | for i in parser.items: |
196 | 196 | if first: |
197 | 197 | ip = resolve_hostname(i.hostname) |
198 | h_id = self.createAndAddHost(ip) | |
199 | i_id = self.createAndAddInterface(h_id, ip, ipv4_address=ip, hostname_resolution=[i.hostname]) | |
200 | s_id = self.createAndAddServiceToInterface(h_id, i_id, i.protocol, ports=[i.port], status="open") | |
201 | ||
198 | h_id = self.createAndAddHost(ip, hostnames=[i.hostname]) | |
199 | s_id = self.createAndAddServiceToHost(h_id, i.protocol, ports=[i.port], status="open") | |
202 | 200 | first = False |
203 | 201 | v_id = self.createAndAddVulnWebToService(h_id, s_id, i.name, ref=i.ref, website=i.hostname, |
204 | 202 | severity=i.severity, desc=i.desc, path=i.url.path, method=i.method, |
258 | 258 | self.framework_version = "1.0.0" |
259 | 259 | self.options = None |
260 | 260 | |
261 | def parseOutputString(self, output, debug=False): | |
261 | def parseOutputString(self, output): | |
262 | 262 | |
263 | 263 | parser = NexposeFullXmlParser(output) |
264 | 264 | |
265 | 265 | for item in parser.items: |
266 | h_id = self.createAndAddHost(item['name'], item['os'], hostnames=item['hostnames']) | |
267 | 266 | pattern = '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$' |
268 | 267 | if not item['mac']: |
269 | 268 | item['mac'] = '0000000000000000' |
270 | match = re.search(pattern, item['mac']) | |
269 | match = re.search(pattern, item['mac']) | |
270 | if match: | |
271 | mac = item['mac'] | |
271 | 272 | else: |
272 | match = re.search(pattern, item['mac']) | |
273 | if match: | |
274 | i_id = self.createAndAddInterface( | |
275 | h_id, | |
276 | item['name'], | |
277 | mac=item['mac'], | |
278 | ipv4_address=item['name'], | |
279 | hostname_resolution=item['hostnames'] | |
280 | ) | |
281 | else: | |
282 | i_id = self.createAndAddInterface( | |
283 | h_id, | |
284 | item['name'], | |
285 | mac=':'.join(item['mac'][i:i + 2] for i in range(0, 12, 2)), | |
286 | ipv4_address=item['name'], | |
287 | hostname_resolution=item['hostnames']) | |
288 | ||
273 | mac = ':'.join(item['mac'][i:i + 2] for i in range(0, 12, 2)) | |
274 | h_id = self.createAndAddHost(item['name'], item['os'], hostnames=item['hostnames'], mac=mac) | |
289 | 275 | for v in item['vulns']: |
290 | 276 | v['data'] = {"vulnerable_since": v['vulnerable_since'], "scan_id": v['scan_id'], "PCI": v['pci']} |
291 | 277 | v_id = self.createAndAddVulnToHost( |
300 | 286 | for s in item['services']: |
301 | 287 | web = False |
302 | 288 | version = s.get("version", "") |
303 | ||
304 | s_id = self.createAndAddServiceToInterface( | |
289 | s_id = self.createAndAddServiceToHost( | |
305 | 290 | h_id, |
306 | i_id, | |
307 | 291 | s['name'], |
308 | 292 | s['protocol'], |
309 | 293 | ports=[str(s['port'])], |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | import re | |
7 | import os | |
8 | import json | |
9 | from faraday_plugins.plugins.plugin import PluginBase | |
10 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | ||
12 | ||
13 | __author__ = "Blas Moyano" | |
14 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
15 | __credits__ = ["Blas Moyano"] | |
16 | __license__ = "" | |
17 | __version__ = "1.0.0" | |
18 | __maintainer__ = "Blas Moyano" | |
19 | __email__ = "[email protected]" | |
20 | __status__ = "Development" | |
21 | ||
22 | ||
23 | class CmdNextNetin(PluginBase): | |
24 | def __init__(self): | |
25 | super().__init__() | |
26 | self.id = "nextnet" | |
27 | self.name = "nextnet" | |
28 | self.plugin_version = "0.0.1" | |
29 | self.version = "5.0.20" | |
30 | self.framework_version = "1.0.0" | |
31 | self.options = None | |
32 | self._current_output = None | |
33 | self._command_regex = re.compile(r'^[.]*?[/]*?nextnet\s+.*?') | |
34 | self._host_ip = None | |
35 | self._info = 0 | |
36 | ||
37 | def parseOutputString(self, output): | |
38 | output_lines = output.split('\n') | |
39 | output_lines = output_lines[:-1] | |
40 | ||
41 | for line in output_lines: | |
42 | json_line = json.loads(line) | |
43 | info_data = json_line.get("info", None) | |
44 | desc = "" | |
45 | mac = None | |
46 | if info_data is not None: | |
47 | desc = f'Domain Tag: {info_data.get("domain", "Not tag info")}' | |
48 | mac = info_data.get("hwaddr") | |
49 | ||
50 | h_id = self.createAndAddHost( | |
51 | json_line.get("host", "0.0.0.0"), | |
52 | os=json_line.get("name", "unknown"), | |
53 | hostnames=json_line.get("nets"), | |
54 | mac=mac | |
55 | ) | |
56 | self.createAndAddServiceToHost( | |
57 | h_id, | |
58 | name=json_line.get("probe", "unknown"), | |
59 | protocol=json_line.get("proto", "tcp"), | |
60 | ports=json_line.get("port", None), | |
61 | description=desc | |
62 | ) | |
63 | return True | |
64 | ||
65 | ||
66 | def createPlugin(): | |
67 | return CmdNextNetin() |
302 | 302 | |
303 | 303 | |
304 | 304 | |
305 | def parseOutputString(self, output, debug=False): | |
305 | def parseOutputString(self, output): | |
306 | 306 | """ |
307 | 307 | This method will discard the output the shell sends, it will read it from |
308 | 308 | the xml where it expects it to be present. |
4 | 4 | |
5 | 5 | """ |
6 | 6 | |
7 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
8 | 7 | import re |
9 | 8 | import os |
10 | ||
9 | from io import BytesIO | |
11 | 10 | |
12 | 11 | try: |
13 | 12 | import xml.etree.cElementTree as ET |
16 | 15 | except ImportError: |
17 | 16 | import xml.etree.ElementTree as ET |
18 | 17 | ETREE_VERSION = ET.VERSION |
18 | from lxml import etree | |
19 | from lxml.etree import XMLParser | |
20 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
19 | 21 | |
20 | 22 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
21 | ||
22 | 23 | current_path = os.path.abspath(os.getcwd()) |
24 | ||
23 | 25 | |
24 | 26 | |
25 | 27 | class NmapXmlParser: |
54 | 56 | """ |
55 | 57 | |
56 | 58 | try: |
57 | return ET.fromstring(xml_output) | |
59 | magical_parser = XMLParser(recover=True) | |
60 | return etree.parse(BytesIO(xml_output), magical_parser) | |
58 | 61 | except SyntaxError as err: |
59 | 62 | #logger.error("SyntaxError: %s." % (err)) |
60 | 63 | return None |
443 | 446 | self.xml_arg_re = re.compile(r"^.*(-oX\s*[^\s]+).*$") |
444 | 447 | self.addSetting("Scan Technique", str, "-sS") |
445 | 448 | |
446 | def parseOutputString(self, output, debug=False): | |
449 | def parseOutputString(self, output): | |
447 | 450 | """ |
448 | 451 | This method will discard the output the shell sends, it will read it |
449 | 452 | from the xml where it expects it to be present. |
463 | 466 | |
464 | 467 | if host.ipv4_address != 'unknown': |
465 | 468 | minterfase = host.ipv4_address |
466 | h_id = self.createAndAddHost(minterfase, host.os) | |
467 | i_id = self.createAndAddInterface( | |
468 | h_id, | |
469 | minterfase, | |
470 | host.mac_address, | |
471 | ipv4_address=host.ipv4_address, | |
472 | hostname_resolution=host.hostnames) | |
473 | 469 | else: |
474 | 470 | minterfase = host.ipv6_address |
475 | h_id = self.createAndAddHost(minterfase, host.os) | |
476 | i_id = self.createAndAddInterface( | |
477 | h_id, | |
478 | minterfase, | |
479 | host.mac_address, | |
480 | ipv6_address=host.ipv6_address, | |
481 | hostname_resolution=host.hostnames) | |
471 | h_id = self.createAndAddHost(minterfase, host.os, mac=host.mac_address, hostnames=host.hostnames) | |
482 | 472 | |
483 | 473 | for v in host.vulns: |
484 | 474 | desc = v.desc |
501 | 491 | srvversion = port.service.product if port.service.product != "unknown" else "" |
502 | 492 | srvversion += " " + port.service.version if port.service.version != "unknown" else "" |
503 | 493 | |
504 | s_id = self.createAndAddServiceToInterface( | |
494 | s_id = self.createAndAddServiceToHost( | |
505 | 495 | h_id, |
506 | i_id, | |
507 | 496 | srvname, |
508 | 497 | port.protocol, |
509 | 498 | ports=[port.number], |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | import socket | |
7 | import json | |
8 | from urllib.parse import urlparse | |
9 | from faraday_plugins.plugins.plugin import PluginMultiLineJsonFormat | |
10 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | ||
12 | __author__ = "Blas Moyano" | |
13 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
14 | __credits__ = ["Blas Moyano"] | |
15 | __license__ = "" | |
16 | __version__ = "0.0.1" | |
17 | __maintainer__ = "Blas Moyano" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class NucleiJsonParser: | |
23 | ||
24 | def __init__(self, json_output): | |
25 | self.list_to_vulns = json_output.split("\n") | |
26 | ||
27 | ||
28 | class NucleiPlugin(PluginMultiLineJsonFormat): | |
29 | """ Handle the Nuclei tool. Detects the output of the tool | |
30 | and adds the information to Faraday. | |
31 | """ | |
32 | ||
33 | def __init__(self): | |
34 | super().__init__() | |
35 | self.id = "nuclei" | |
36 | self.name = "Nuclei" | |
37 | self.plugin_version = "0.1" | |
38 | self.version = "0.0.1" | |
39 | self.json_keys = {"matched", "template"} | |
40 | ||
41 | def parseOutputString(self, output, debug=False): | |
42 | parser = NucleiJsonParser(output) | |
43 | matched_list = [] | |
44 | matched_json = {} | |
45 | for vuln in parser.list_to_vulns: | |
46 | if vuln != '': | |
47 | json_vuln = json.loads(vuln) | |
48 | matched = json_vuln.get('matched', None) | |
49 | ||
50 | if matched is not None: | |
51 | url_parser = urlparse(matched) | |
52 | url_scheme = f'{url_parser.scheme}://{url_parser.hostname}' | |
53 | ||
54 | if url_scheme in matched_list: | |
55 | matched_json[url_scheme].append(json_vuln) | |
56 | else: | |
57 | matched_list.append(url_scheme) | |
58 | matched_json[url_scheme] = [json_vuln] | |
59 | ||
60 | for url in matched_list: | |
61 | url_data = urlparse(url) | |
62 | url_name = url_data.hostname | |
63 | url_protocol = url_data.scheme | |
64 | ip = resolve_hostname(url_name) | |
65 | host_id = self.createAndAddHost( | |
66 | name=ip, | |
67 | hostnames=[url_name]) | |
68 | port = 80 | |
69 | if url_parser.scheme == 'https': | |
70 | port = 443 | |
71 | ||
72 | service_id = self.createAndAddServiceToHost( | |
73 | host_id, | |
74 | name=url_parser.scheme, | |
75 | ports=port, | |
76 | protocol="tcp", | |
77 | status='open', | |
78 | version='', | |
79 | description='') | |
80 | ||
81 | for info_vuln in matched_json[url]: | |
82 | desc = f'{info_vuln.get("template", None)} - {info_vuln.get("author", None)}' | |
83 | if info_vuln.get("author", None): | |
84 | ref = [f"author: {info_vuln.get('author', None)}"] | |
85 | else: | |
86 | ref = None | |
87 | self.createAndAddVulnWebToService( | |
88 | host_id, | |
89 | service_id, | |
90 | name=info_vuln.get('template', ""), | |
91 | desc=info_vuln.get('description', info_vuln.get('name', None)), | |
92 | ref=ref, | |
93 | severity=info_vuln.get('severity', ""), | |
94 | website=url, | |
95 | request=info_vuln.get('request', None), | |
96 | response=info_vuln.get('response', None), | |
97 | method=info_vuln.get('type', None), | |
98 | data=info_vuln.get('matcher_name', info_vuln.get('name', None)), | |
99 | external_id=info_vuln.get('template', "")) | |
100 | ||
101 | ||
102 | def createPlugin(): | |
103 | return NucleiPlugin() | |
104 | ||
105 |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | """ | |
3 | Faraday Penetration Test IDE | |
4 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
5 | See the file 'doc/LICENSE' for the license information | |
6 | """ | |
7 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
8 | from datetime import datetime | |
9 | from lxml import etree | |
10 | import ipaddress | |
11 | ||
12 | try: | |
13 | import xml.etree.cElementTree as ET | |
14 | except ImportError: | |
15 | import xml.etree.ElementTree as ET | |
16 | ||
17 | __author__ = 'Blas Moyano' | |
18 | __copyright__ = 'Copyright 2020, Faraday Project' | |
19 | __credits__ = ['Blas Moyano'] | |
20 | __license__ = '' | |
21 | __version__ = '1.0.0' | |
22 | __status__ = 'Development' | |
23 | ||
24 | ||
25 | class OpenScapParser: | |
26 | def __init__(self, xml_output): | |
27 | self.tree = self.parse_xml(xml_output) | |
28 | ||
29 | if self.tree is not None: | |
30 | self.rule_date = self.get_parser_rule(self.tree.findall('Rule', self.tree.nsmap)) | |
31 | self.result_data = self.get_parser_result(self.tree.findall('TestResult', self.tree.nsmap)) | |
32 | self.tree = None | |
33 | ||
34 | def parse_xml(self, xml_output): | |
35 | try: | |
36 | parser = etree.XMLParser(recover=True) | |
37 | tree = etree.fromstring(xml_output, parser=parser) | |
38 | except SyntaxError as err: | |
39 | print('SyntaxError In xml: %s. %s' % (err, xml_output)) | |
40 | return None | |
41 | return tree | |
42 | ||
43 | def get_parser_rule(self, tree): | |
44 | list_rules = [] | |
45 | for rule in tree: | |
46 | title = rule.find('title', self.tree.nsmap) | |
47 | ident = rule.find('ident', self.tree.nsmap) | |
48 | check = rule.find('check', self.tree.nsmap) | |
49 | check_ref = rule.find('check/check-content-ref', self.tree.nsmap) | |
50 | try: | |
51 | ident_result = ident.text | |
52 | except AttributeError: | |
53 | ident_result = "" | |
54 | json_rule = { | |
55 | "rule_id": rule.attrib.get('id', None), | |
56 | "rule_sev": rule.attrib.get('severity', None), | |
57 | "rule_title": title.text, | |
58 | "rule_ident": ident_result, | |
59 | "rule_check": check.attrib.get('system', None), | |
60 | "rule_ref_name": check_ref.attrib.get('name', None), | |
61 | "rule_ref_href": check_ref.attrib.get('href', None) | |
62 | } | |
63 | list_rules.append(json_rule) | |
64 | return list_rules | |
65 | ||
66 | def get_parser_result(self, tree): | |
67 | list_result = [] | |
68 | list_ip = [] | |
69 | list_mac = [] | |
70 | list_data = [] | |
71 | for result in tree: | |
72 | title = result.find('title', self.tree.nsmap) | |
73 | target = result.find('target', self.tree.nsmap) | |
74 | ips = result.findall('target-address', self.tree.nsmap) | |
75 | target_facts = result.findall('target-facts/fact', self.tree.nsmap) | |
76 | rule_result = result.findall('rule-result', self.tree.nsmap) | |
77 | ||
78 | for ip in ips: | |
79 | list_ip.append(ip.text) | |
80 | ||
81 | for mac in target_facts: | |
82 | fact_name = mac.attrib.get('name', None) | |
83 | if fact_name == 'urn:xccdf:fact:ethernet:MAC': | |
84 | list_mac.append(mac.text) | |
85 | ||
86 | for data in rule_result: | |
87 | list_ident = [] | |
88 | idents = data.findall('ident', self.tree.nsmap) | |
89 | check = data.find('check', self.tree.nsmap) | |
90 | check_ref = data.find('check/check-content-ref', self.tree.nsmap) | |
91 | ||
92 | for ident in idents: | |
93 | json_ident = { | |
94 | "system": data.attrib.get('system', None), | |
95 | "text": ident.text | |
96 | } | |
97 | list_ident.append(json_ident) | |
98 | ||
99 | json_data = { | |
100 | "id": data.attrib.get('idref', None), | |
101 | "time": data.attrib.get('time', None), | |
102 | "severity": data.attrib.get('severity', None), | |
103 | "ident": list_ident, | |
104 | "check": check.attrib.get('system', None), | |
105 | "ref_name": check_ref.attrib.get('name', None), | |
106 | "ref_href": check_ref.attrib.get('href', None) | |
107 | } | |
108 | ||
109 | status = data.find('result', self.tree.nsmap) | |
110 | if status.text == 'fail': | |
111 | list_data.append(json_data) | |
112 | ||
113 | json_result = { | |
114 | "id": result.attrib.get('id', None), | |
115 | "start_time": result.attrib.get('start-time', None), | |
116 | "end_time": result.attrib.get('end-time', None), | |
117 | "result_title": title.text, | |
118 | "target": target.text, | |
119 | "ips": list_ip, | |
120 | "mac": list_mac, | |
121 | "rule_result": list_data | |
122 | } | |
123 | list_result.append(json_result) | |
124 | return list_result | |
125 | ||
126 | ||
127 | class OpenScapPlugin(PluginXMLFormat): | |
128 | def __init__(self): | |
129 | super().__init__() | |
130 | self.identifier_tag = "Benchmark" | |
131 | self.id = 'OpenScap' | |
132 | self.name = 'OpenScap XML Output Plugin' | |
133 | self.plugin_version = '1.0.0' | |
134 | self.version = '1.0.0' | |
135 | self.framework_version = '1.0.0' | |
136 | self.options = None | |
137 | self.protocol = None | |
138 | self.port = '80' | |
139 | ||
140 | def parseOutputString(self, output): | |
141 | parser = OpenScapParser(output) | |
142 | ips = [] | |
143 | ||
144 | for ip in parser.result_data[0]['ips']: | |
145 | len_start_port = ip.find(":") | |
146 | if len_start_port > -1: | |
147 | ip = ip[:len_start_port] | |
148 | try: | |
149 | ipaddress.ip_address(ip) | |
150 | ips.append(ip) | |
151 | except ValueError: | |
152 | pass | |
153 | for ip in ips: | |
154 | if ip != '127.0.0.1': | |
155 | ip = ip | |
156 | ips.remove(ip) | |
157 | break | |
158 | ||
159 | list_mac = parser.result_data[0]['mac'] | |
160 | for mac in list_mac: | |
161 | if mac != '00:00:00:00:00:00': | |
162 | mac_address = mac | |
163 | list_mac.remove(mac_address) | |
164 | break | |
165 | ||
166 | description = f'Title: {parser.result_data[0]["result_title"]} ' \ | |
167 | f'Ips: {ips} ' \ | |
168 | f'Macs: {list_mac}' | |
169 | host_id = self.createAndAddHost( | |
170 | name=ip, | |
171 | hostnames=[parser.result_data[0]['target']], | |
172 | description=description, | |
173 | mac=mac_address | |
174 | ) | |
175 | ||
176 | rules_fail = parser.result_data[0]['rule_result'] | |
177 | if rules_fail: | |
178 | info_rules = parser.rule_date | |
179 | severity = 0 | |
180 | ||
181 | for rule in rules_fail: | |
182 | vuln_run_date = datetime.strptime( | |
183 | rule['time'].replace('T', ' '), | |
184 | '%Y-%m-%d %H:%M:%S') | |
185 | ||
186 | if rule['severity'] == 'low': | |
187 | severity = 1 | |
188 | elif rule['severity'] == 'medium': | |
189 | severity = 2 | |
190 | elif rule['severity'] == 'high': | |
191 | severity = 3 | |
192 | ||
193 | desc = f'name: {rule["ref_name"]} - link: {rule["ref_href"]}' | |
194 | ||
195 | for info in info_rules: | |
196 | if rule['id'] == info['rule_id']: | |
197 | vuln_name = info['rule_title'] | |
198 | vuln_data = info['rule_check'] | |
199 | vuln_ref = info['rule_ident'] | |
200 | ||
201 | self.createAndAddVulnToHost( | |
202 | host_id, | |
203 | vuln_name, | |
204 | desc=desc, | |
205 | ref=[vuln_ref], | |
206 | severity=severity, | |
207 | data=vuln_data, | |
208 | external_id=rule['id'], | |
209 | run_date=vuln_run_date) | |
210 | ||
211 | ||
212 | def createPlugin(): | |
213 | return OpenScapPlugin() |
334 | 334 | report_path = kwargs.get("report_path", "") |
335 | 335 | with open(report_path) as f: |
336 | 336 | output = f.read() |
337 | return re.search("OpenVAS", output) is not None or re.search('<omp>', output) is not None | |
337 | return re.search("OpenVAS", output) is not None \ | |
338 | or re.search('<omp>', output) is not None\ | |
339 | or re.search('<owner>', output) is not None | |
338 | 340 | return False |
339 | 341 | |
340 | def parseOutputString(self, output, debug=False): | |
342 | def parseOutputString(self, output): | |
341 | 343 | """ |
342 | 344 | This method will discard the output the shell sends, it will read it |
343 | 345 | from the xml where it expects it to be present. |
344 | ||
345 | NOTE: if 'debug' is true then it is being run from a test case and the | |
346 | output being sent is valid. | |
347 | 346 | """ |
348 | 347 | parser = OpenvasXmlParser(output, self.logger) |
349 | 348 | web = False |
443 | 442 | else: |
444 | 443 | return False |
445 | 444 | |
446 | ||
447 | 445 | def setHost(self): |
448 | 446 | pass |
449 | 447 |
44 | 44 | return |
45 | 45 | # Configuration initial. |
46 | 46 | hostId = self.createAndAddHost("pasteAnalyzer") |
47 | interfaceId = self.createAndAddInterface(hostId, "Results") | |
48 | serviceId = self.createAndAddServiceToInterface( | |
47 | serviceId = self.createAndAddServiceToHost( | |
49 | 48 | hostId, |
50 | interfaceId, | |
51 | 49 | "Web", |
52 | 50 | "TcpHTTP", |
53 | 51 | ['80'] |
50 | 50 | url_parsed = urlparse(url) |
51 | 51 | address = resolve_hostname(url_parsed.netloc) |
52 | 52 | host = self.createAndAddHost(address) |
53 | iface = self.createAndAddInterface( | |
54 | host, address, ipv4_address=address) | |
55 | service = self.createAndAddServiceToInterface(host, iface, "http", protocol="tcp", ports=[80]) | |
53 | service = self.createAndAddServiceToHost(host, "http", protocol="tcp", ports=[80]) | |
56 | 54 | self.createAndAddNoteToService( |
57 | 55 | host, |
58 | 56 | service, |
30 | 30 | self.version = "1.0.0" |
31 | 31 | self._command_regex = re.compile(r'^(sudo ping|ping|sudo ping6|ping6)\s+.*?') |
32 | 32 | |
33 | def parseOutputString(self, output, debug=False): | |
33 | def parseOutputString(self, output): | |
34 | 34 | |
35 | 35 | reg = re.search(r"PING ([\w\.-:]+)( |)\(([\w\.:]+)\)", output) |
36 | 36 | if re.search("0 received|unknown host", output) is None and reg is not None: |
37 | 37 | |
38 | 38 | ip_address = reg.group(3) |
39 | 39 | hostname = reg.group(1) |
40 | ||
41 | h_id = self.createAndAddHost(ip_address) | |
42 | if self._isIPV4(ip_address): | |
43 | i_id = self.createAndAddInterface( | |
44 | h_id, ip_address, ipv4_address=ip_address, hostname_resolution=[hostname]) | |
45 | else: | |
46 | self.createAndAddInterface( | |
47 | h_id, ip_address, ipv6_address=ip_address, hostname_resolution=[hostname]) | |
48 | ||
40 | h_id = self.createAndAddHost(ip_address, hostnames=[hostname]) | |
49 | 41 | return True |
50 | 42 | |
51 | 43 | def _isIPV4(self, ip): |
37 | 37 | self._host_ip = None |
38 | 38 | self._port = "23" |
39 | 39 | |
40 | def parseOutputString(self, output, debug=False): | |
40 | def parseOutputString(self, output): | |
41 | 41 | |
42 | 42 | host_info = re.search( |
43 | 43 | r"(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)", output) |
48 | 48 | for host in output.splitlines(): |
49 | 49 | if host != "": |
50 | 50 | h_id = self.createAndAddHost(host) |
51 | i_id = self.createAndAddInterface( | |
52 | h_id, host, ipv4_address=host) | |
53 | s_id = self.createAndAddServiceToInterface(h_id, i_id, str(self._port), | |
54 | "tcp", | |
55 | ports=[self._port], | |
56 | status="open", | |
57 | version="", | |
58 | description="") | |
59 | if debug is True: | |
60 | self.logger.info("Debug is active") | |
61 | ||
51 | s_id = self.createAndAddServiceToHost(h_id, str(self._port), "tcp", ports=[self._port], | |
52 | status="open", version="", description="") | |
62 | 53 | return True |
63 | 54 | |
64 | 55 | def processCommandString(self, username, current_path, command_string): |
345 | 345 | self.options = None |
346 | 346 | self.open_options = {"mode": "r", "encoding": "utf-8"} |
347 | 347 | |
348 | def parseOutputString(self, output, debug=False): | |
348 | def parseOutputString(self, output): | |
349 | 349 | |
350 | 350 | parser = QualysguardXmlParser(output) |
351 | 351 |
54 | 54 | for self.results_tags in tree.find('VULNERABILITY_LIST'): |
55 | 55 | yield Results(self.results_tags) |
56 | 56 | |
57 | ||
57 | 58 | class Appendix(): |
58 | 59 | def __init__(self, appendix_tags): |
59 | 60 | if appendix_tags.tag == 'SCAN_LIST': |
60 | 61 | self.lista_scan = self.get_scan(appendix_tags.find('SCAN')) |
62 | ||
61 | 63 | elif appendix_tags.tag == 'WEBAPP': |
62 | 64 | self.lista_webapp = self.get_webapp(appendix_tags) |
63 | 65 | |
123 | 125 | for host_create in parser.info_appendix: |
124 | 126 | self.scan_list_result.append(host_create) |
125 | 127 | |
126 | self.credential = self.scan_list_result[0].lista_scan.get('AUTHENTICATION_RECORD') | |
127 | os = self.scan_list_result[1].lista_webapp.get('OPERATING_SYSTEM') | |
128 | ||
129 | if self.scan_list_result[1].lista_webapp.get('URL'): | |
130 | initial_url = self.scan_list_result[1].lista_webapp.get('URL') | |
131 | parsed_url = urlparse(initial_url) | |
132 | hostnames = [parsed_url.netloc] | |
128 | for k in self.scan_list_result: | |
129 | if 'result_scan' in k.__dict__: | |
130 | self.credential = k.lista_scan.get('AUTHENTICATION_RECORD') | |
131 | elif 'result_webapp' in k.__dict__: | |
132 | operating_system = k.lista_webapp.get('OPERATING_SYSTEM') | |
133 | if k.lista_webapp.get('URL'): | |
134 | initial_url = k.lista_webapp.get('URL') | |
135 | parsed_url = urlparse(initial_url) | |
136 | hostnames = [parsed_url.netloc] | |
133 | 137 | |
134 | 138 | glossary = [] |
135 | 139 | for glossary_qid in parser.info_glossary: |
138 | 142 | for v in parser.info_results: |
139 | 143 | url = urlparse(v.dict_result_vul.get('URL')) |
140 | 144 | |
141 | host_id = self.createAndAddHost(name=url.netloc, os=os, hostnames=hostnames) | |
145 | host_id = self.createAndAddHost(name=url.netloc, os=operating_system, hostnames=hostnames) | |
142 | 146 | |
143 | 147 | vuln_scan_id = v.dict_result_vul.get('QID') |
144 | 148 | |
150 | 154 | raw_severity = int(vuln_data.get('SEVERITY', 0)) |
151 | 155 | vuln_severity = raw_severity - 1 |
152 | 156 | |
153 | run_date = parse(v.dict_result_vul.get('FIRST_TIME_DETECTED')) | |
157 | if not v.dict_result_vul.get('FIRST_TIME_DETECTED'): | |
158 | run_date = '' | |
159 | else: | |
160 | run_date = parse(v.dict_result_vul.get('FIRST_TIME_DETECTED')) | |
161 | ||
154 | 162 | vuln_resolution = vuln_data.get('SOLUTION') |
155 | 163 | |
156 | 164 | vuln_ref = [] |
0 | import re | |
1 | from collections import defaultdict | |
2 | ||
3 | from faraday_plugins.plugins.plugin import PluginBase | |
4 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
5 | ||
6 | ||
7 | class RDPScanPlugin(PluginBase): | |
8 | ||
9 | def __init__(self): | |
10 | super().__init__() | |
11 | self.identifier_tag = "rdpscan" | |
12 | self.id = "rdpscan" | |
13 | self.name = "rdpscan" | |
14 | self._command_regex = re.compile(r'^(sudo rdpscan|rdpscan|\.\/rdpscan)\s+.*?') | |
15 | ||
16 | def parseOutputString(self, output): | |
17 | services = defaultdict(set) | |
18 | for info in output.split('\n'): | |
19 | if info: | |
20 | ip, status, data = info.split('-', 2) | |
21 | ip = ip.strip() | |
22 | status = status.strip() | |
23 | data = data.strip() | |
24 | if status.lower() == 'unknown': | |
25 | continue | |
26 | ||
27 | host_id = self.createAndAddHost(ip) | |
28 | service_id = self.createAndAddServiceToHost( | |
29 | host_id=host_id, | |
30 | name='rdp', | |
31 | ports=3389, | |
32 | protocol='tcp', | |
33 | ) | |
34 | if status.lower() == 'vulnerable': | |
35 | description = "A remote code execution vulnerability exists in Remote Desktop Services formerly known as Terminal Services when an unauthenticated attacker connects to the target system using RDP and sends specially crafted requests, aka 'Remote Desktop Services Remote Code Execution Vulnerability'. " | |
36 | self.createAndAddVulnToService( | |
37 | host_id=host_id, | |
38 | service_id=service_id, | |
39 | name='Remote Desktop Services Remote Code Execution Vulnerability', | |
40 | desc=description, | |
41 | ref=['CVE-2019-0708'] | |
42 | ) | |
43 | ||
44 | ||
45 | def createPlugin(): | |
46 | return RDPScanPlugin() |
182 | 182 | self.options = None |
183 | 183 | |
184 | 184 | |
185 | def parseOutputString(self, output, debug=False): | |
185 | def parseOutputString(self, output): | |
186 | 186 | |
187 | 187 | parser = RetinaXmlParser(output) |
188 | 188 | for item in parser.items: |
189 | h_id = self.createAndAddHost(item.ip, item.os) | |
190 | hostname = item.hostname if item.hostname else item.ip | |
191 | i_id = self.createAndAddInterface( | |
192 | h_id, item.ip, ipv4_address=item.ip, hostname_resolution=[hostname]) | |
189 | hostname = item.hostname if item.hostname else None | |
190 | h_id = self.createAndAddHost(item.ip, item.os,hostnames=[hostname]) | |
193 | 191 | |
194 | 192 | if not item.netbiosname == 'N/A': |
195 | 193 | self.createAndAddNoteToHost( |
203 | 201 | if k: |
204 | 202 | for v in vulns: |
205 | 203 | web = False |
206 | s_id = self.createAndAddServiceToInterface(h_id, i_id, 'unknown', | |
207 | v.protocol.lower(), | |
208 | ports=[str(v.port)], | |
209 | status="open") | |
204 | s_id = self.createAndAddServiceToHost(h_id, 'unknown', v.protocol.lower(), ports=[str(v.port)], | |
205 | status="open") | |
210 | 206 | if v.port in ['80', '443'] or re.search("ssl|http", v.name.lower()): |
211 | 207 | web = True |
212 | 208 | else: |
73 | 73 | parser = ReverseraiderParser(output) |
74 | 74 | for item in parser.items: |
75 | 75 | h_id = self.createAndAddHost(item['ip']) |
76 | i_id = self.createAndAddInterface(h_id, item['ip'], ipv4_address=item['ip']) | |
77 | 76 | del parser |
78 | 77 | |
79 | 78 |
165 | 165 | |
166 | 166 | ip = resolve_hostname(host) |
167 | 167 | |
168 | h_id = self.createAndAddHost(ip) | |
169 | i_id = self.createAndAddInterface( | |
170 | h_id, | |
171 | ip, | |
172 | ipv4_address=ip, | |
173 | hostname_resolution=[host]) | |
174 | ||
175 | s_id = self.createAndAddServiceToInterface( | |
176 | h_id, | |
177 | i_id, | |
178 | "http", | |
179 | "tcp", | |
180 | ports=[port], | |
181 | status="open") | |
168 | h_id = self.createAndAddHost(ip, hostnames=[host]) | |
169 | s_id = self.createAndAddServiceToHost(h_id, "http", "tcp", ports=[port], status="open") | |
182 | 170 | |
183 | 171 | hostc[sample["url"]] = { |
184 | 172 | 'h_id': h_id, |
186 | 174 | 'port': port, |
187 | 175 | 'host': host, |
188 | 176 | 'protocol': protocol, |
189 | 'i_id': i_id, | |
190 | 177 | 's_id': s_id} |
191 | 178 | |
192 | 179 | try: |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | ||
7 | # I'm Py3⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2019 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | import json | |
7 | from faraday_plugins.plugins.plugin import PluginJsonFormat | |
8 | from urllib.parse import urlparse | |
9 | import os | |
10 | ||
11 | ||
12 | __author__ = "Blas Moyano" | |
13 | __copyright__ = "Copyright (c) 2019, Infobyte LLC" | |
14 | __credits__ = ["Blas Moyano"] | |
15 | __license__ = "" | |
16 | __version__ = "0.0.1" | |
17 | __maintainer__ = "Blas Moyano" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class SourceclearJsonParser: | |
23 | def __init__(self, json_output): | |
24 | self.json_data = json.loads(json_output) | |
25 | ||
26 | def parse_url(self, url): | |
27 | url_parse = urlparse(url) | |
28 | protocol = url_parse.scheme | |
29 | hostname = url_parse.netloc | |
30 | port = url_parse.port | |
31 | ||
32 | if port is None: | |
33 | if protocol == 'https': | |
34 | port = 443 | |
35 | elif protocol == 'http': | |
36 | if not port: | |
37 | port = 80 | |
38 | ||
39 | return {'protocol': protocol, 'hostname': hostname, 'port': port} | |
40 | ||
41 | ||
42 | class SourceclearPlugin(PluginJsonFormat): | |
43 | """ Handle the Sourceclear tool. Detects the output of the tool | |
44 | and adds the information to Faraday. | |
45 | """ | |
46 | ||
47 | def __init__(self): | |
48 | super().__init__() | |
49 | self.id = "sourceclear" | |
50 | self.name = "Sourceclear" | |
51 | self.plugin_version = "0.1" | |
52 | self.version = "0.0.1" | |
53 | self.json_keys = {"metadata", "records"} | |
54 | ||
55 | def parseOutputString(self, output, debug=False): | |
56 | parser = SourceclearJsonParser(output) | |
57 | ||
58 | for records in parser.json_data['records']: | |
59 | vulns = records['vulnerabilities'] | |
60 | libraries = records['libraries'] | |
61 | ||
62 | for vuln in vulns: | |
63 | v_name = vuln['title'] | |
64 | v_desc = vuln['overview'] | |
65 | v_ref = "CVSS: {}".format(vuln['cvssScore']) | |
66 | v_data = vuln['libraries'] | |
67 | v_website = vuln['_links']['html'] | |
68 | url_data = parser.parse_url(v_website) | |
69 | for refs in vuln['libraries']: | |
70 | ref = refs['_links']['ref'] | |
71 | num_versions = ref.find("/versions") | |
72 | _, num_libraries = os.path.split(ref[:num_versions]) | |
73 | name_librarie = libraries[int(num_libraries)]['name'] | |
74 | version_librarie = libraries[int(num_libraries)]['versions'][0]['version'] | |
75 | host_name = f'{name_librarie}{version_librarie}' | |
76 | ||
77 | h_id = self.createAndAddHost(name=host_name, scan_template=records['metadata']['recordType']) | |
78 | s_id = self.createAndAddServiceToHost(h_id, "Sourceclear", protocol=url_data['protocol'], | |
79 | ports=url_data['port'], status='open') | |
80 | self.createAndAddVulnWebToService(h_id, s_id, name=v_name, desc=v_desc, ref=[v_ref], data=v_data, | |
81 | website=v_website) | |
82 | ||
83 | ||
84 | def createPlugin(): | |
85 | return SourceclearPlugin() |
38 | 38 | if output_rexeg_match: |
39 | 39 | credentials, address = line.split("@") |
40 | 40 | host = self.createAndAddHost(address) |
41 | iface = self.createAndAddInterface( | |
42 | host, address, ipv4_address=address) | |
43 | service = self.createAndAddServiceToInterface( | |
44 | host, iface, "ssh", protocol="tcp", ports=[22] | |
45 | ) | |
41 | service = self.createAndAddServiceToHost(host, "ssh", protocol="tcp", ports=[22]) | |
46 | 42 | username, password = credentials.split(":") |
47 | 43 | cred = self.createAndAddCredToService( |
48 | 44 | host, service, username, password) |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | import re | |
7 | import json | |
8 | from urllib.parse import urlparse | |
9 | from faraday_plugins.plugins.plugin import PluginJsonFormat | |
10 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | ||
12 | __author__ = "Blas Moyano" | |
13 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
14 | __credits__ = ["Blas Moyano"] | |
15 | __license__ = "" | |
16 | __version__ = "0.0.1" | |
17 | __maintainer__ = "Blas Moyano" | |
18 | __email__ = "[email protected]" | |
19 | __status__ = "Development" | |
20 | ||
21 | ||
22 | class SslLabsJsonParser: | |
23 | ||
24 | def __init__(self, json_output): | |
25 | list_data = json.loads(json_output) | |
26 | self.json_data = list_data[0] | |
27 | ||
28 | def host_info(self, data): | |
29 | host_information = { | |
30 | "url": data.get('host', 'Not Info'), | |
31 | "description_host": "SSL Labs", | |
32 | "port": data.get('port', 0), | |
33 | "protocol": data.get('protocol', 'Not Info'), | |
34 | "status": data.get('status', 'Not Info'), | |
35 | "version": data.get('engineVersion', '0.0.0'), | |
36 | "run_date": data.get('startTime', 'Not Info') | |
37 | } | |
38 | return host_information | |
39 | ||
40 | def get_ip(self, data): | |
41 | for ip in data: | |
42 | return ip.get('ipAddress', '0.0.0.0') | |
43 | ||
44 | def get_vulns(self, data): | |
45 | chain = data.get('chain', None) | |
46 | vuln_list = [] | |
47 | policies_list = [data.get('hstsPolicy', 'No Information'), | |
48 | data.get('hpkpPolicy', 'No Information'), | |
49 | data.get('hpkpRoPolicy', 'No Information')] | |
50 | ||
51 | for vulns in chain['certs']: | |
52 | vuln = { | |
53 | "name": vulns.get('issuerLabel', 'No Information'), | |
54 | "desc": vulns.get('issuerSubject', 'No Information'), | |
55 | "data": f'SHA1HASH: {vulns.get("sha1Hash", "No Information")}' | |
56 | f'PINSHA256: {vulns.get("pinSha256", "No Information")}' | |
57 | f'RAW: {vulns.get("raw", "No Information")}', | |
58 | "policy": policies_list | |
59 | } | |
60 | vuln_list.append(vuln) | |
61 | return vuln_list | |
62 | ||
63 | ||
64 | class SslLabsPlugin(PluginJsonFormat): | |
65 | """ Handle the SSL Labs tool. Detects the output of the tool | |
66 | and adds the information to Faraday. | |
67 | """ | |
68 | ||
69 | def __init__(self): | |
70 | super().__init__() | |
71 | self.id = "ssllabs" | |
72 | self.name = "SSL Labs" | |
73 | self.plugin_version = "0.1" | |
74 | self.version = "3.4.5" | |
75 | self.json_keys = {'engineVersion', 'criteriaVersion', 'endpoints'} | |
76 | ||
77 | ||
78 | def parseOutputString(self, output): | |
79 | parser = SslLabsJsonParser(output) | |
80 | host = parser.host_info(parser.json_data) | |
81 | ||
82 | host_id = self.createAndAddHost( | |
83 | name=parser.get_ip(parser.json_data['endpoints']), | |
84 | hostnames=[host['url']], | |
85 | description=host['description_host']) | |
86 | ||
87 | service_id = self.createAndAddServiceToHost( | |
88 | host_id=host_id, | |
89 | name=host['url'], | |
90 | protocol=host['protocol'], | |
91 | ports=host['port'], | |
92 | status=host['status'], | |
93 | version=host['version']) | |
94 | ||
95 | vulns = parser.get_vulns(parser.json_data['endpoints'][0]['details']) | |
96 | ||
97 | for vuln in vulns: | |
98 | policy_info = f"Long max age: {vuln['policy'][0]['LONG_MAX_AGE']}" \ | |
99 | f"Status: {vuln['policy'][0]['status']} | {vuln['policy'][1]['status']} | {vuln['policy'][2]['status']} " \ | |
100 | f"directives: {vuln['policy'][0]['directives']} | {vuln['policy'][1]['directives']} | {vuln['policy'][2]['directives']}" \ | |
101 | f"pins: {vuln['policy'][1]['directives']} | {vuln['policy'][2]['directives']} " \ | |
102 | f"matchedPins: {vuln['policy'][1]['matchedPins']} | {vuln['policy'][2]['matchedPins']} " | |
103 | ||
104 | self.createAndAddVulnToService(host_id, | |
105 | service_id=service_id, | |
106 | name=vuln['name'], | |
107 | desc=vuln['desc'], | |
108 | policyviolations=[policy_info], | |
109 | data=vuln['data']) | |
110 | ||
111 | ||
112 | def createPlugin(): | |
113 | return SslLabsPlugin() |
4 | 4 | from lxml import etree as ET |
5 | 5 | except ImportError: |
6 | 6 | import xml.etree.ElementTree as ET |
7 | ||
8 | 7 | |
9 | 8 | WEAK_CIPHER_LIST = [ |
10 | 9 | "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", |
14 | 13 | "TLS_RSA_WITH_AES_256_CBC_SHA", |
15 | 14 | "TLS_RSA_WITH_AES_256_CBC_SHA256", |
16 | 15 | "TLS_RSA_WITH_AES_256_GCM_SHA384", |
17 | "TLS_RSA_WITH_3DES_EDE_CBC_SHA", | |
16 | "TLS_RSA_WITH_3DES_EDE_CBC_SHA", | |
18 | 17 | "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", |
19 | "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" | |
18 | "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" | |
20 | 19 | ] |
21 | 20 | |
22 | 21 | |
30 | 29 | self.heart_bleed = self.get_heartbleed(self.parser) |
31 | 30 | self.open_ssl_ccs = self.get_openssl_ccs(self.parser) |
32 | 31 | |
33 | def parse_xml(self, xml_output): | |
32 | def parse_xml(self, xml_output): | |
34 | 33 | try: |
35 | 34 | tree = ET.fromstring(xml_output) |
36 | 35 | return tree |
40 | 39 | |
41 | 40 | def get_target(self, tree): |
42 | 41 | return tree.xpath('//target') |
43 | ||
42 | ||
44 | 43 | def get_hostname_validation(self, tree): |
45 | 44 | return tree.xpath('//hostnameValidation') |
46 | 45 | |
72 | 71 | if cipher.attrib['name'] in WEAK_CIPHER_LIST: |
73 | 72 | if not cipher.attrib['name'] in weak_cipher[protocol.tag]: |
74 | 73 | weak_cipher[protocol.tag].append(cipher.attrib['name']) |
75 | ||
74 | ||
76 | 75 | return weak_cipher |
77 | 76 | |
78 | 77 | def get_heartbleed(self, tree): |
87 | 86 | def __init__(self): |
88 | 87 | super().__init__() |
89 | 88 | self.identifier_tag = "document" |
90 | self.id = "Sslyze" | |
89 | self.id = "Sslyze XML" | |
91 | 90 | self.name = "Sslyze Plugin" |
92 | 91 | self.plugin_version = "0.0.1" |
93 | 92 | self.version = "2.0.6" |
94 | 93 | self.framework_version = "1.0.0" |
95 | 94 | self.options = None |
96 | 95 | self._current_output = None |
97 | self._command_regex = re.compile(r'^(sudo sslyze|sslyze|\.\/sslyze)\s+.*?') | |
98 | self.xml_arg_re = re.compile(r"^.*(--xml_output\s*[^\s]+).*$") | |
99 | 96 | self._use_temp_file = True |
100 | 97 | self._temp_file_extension = "xml" |
101 | 98 | |
107 | 104 | return re.search("SSLyzeVersion", output) is not None |
108 | 105 | return False |
109 | 106 | |
110 | def parseOutputString(self, output, debug=False): | |
107 | def parseOutputString(self, output): | |
111 | 108 | parser = SslyzeXmlParser(output) |
112 | 109 | host = parser.target[0].attrib['host'] |
113 | 110 | ip = parser.target[0].attrib['ip'] |
114 | 111 | port = parser.target[0].attrib['port'] |
115 | 112 | protocol = parser.target[0].attrib['tlsWrappedProtocol'] |
116 | 113 | cipher = parser.cipher_suite |
117 | ||
114 | ||
118 | 115 | # Creating host |
119 | 116 | host_id = self.createAndAddHost(ip) |
120 | # Creating service CHANGE NAME | |
117 | # Creating service CHANGE NAME | |
121 | 118 | service_id = self.createAndAddServiceToHost( |
122 | host_id, | |
119 | host_id, | |
123 | 120 | name=protocol, |
124 | protocol=protocol, | |
121 | protocol=protocol, | |
125 | 122 | ports=[port], |
126 | ) | |
127 | ||
123 | ) | |
124 | ||
128 | 125 | # Checking if certificate matches |
129 | 126 | certificate = parser.certificate[0].attrib['certificateMatchesServerHostname'] |
130 | 127 | server_hostname = parser.certificate[0].attrib['serverHostname'] |
131 | 128 | if certificate.lower() == 'false': |
132 | 129 | self.createAndAddVulnToService( |
133 | host_id, | |
134 | service_id, | |
135 | name="Certificate mismatch", | |
136 | desc="Certificate does not match server hostname {}".format(server_hostname), | |
130 | host_id, | |
131 | service_id, | |
132 | name="Certificate mismatch", | |
133 | desc="Certificate does not match server hostname {}".format(server_hostname), | |
137 | 134 | severity="info") |
138 | #Ciphers | |
135 | # Ciphers | |
139 | 136 | cipher = parser.cipher_suite |
140 | 137 | |
141 | 138 | for key in cipher: |
142 | 139 | for value in cipher[key]: |
143 | 140 | self.createAndAddVulnToService( |
144 | host_id, | |
145 | service_id, | |
141 | host_id, | |
142 | service_id, | |
146 | 143 | name=value, |
147 | desc="In protocol [{}], weak cipher suite: {}".format(key, value), | |
144 | desc="In protocol [{}], weak cipher suite: {}".format(key, value), | |
148 | 145 | severity="low") |
149 | ||
150 | #Heartbleed | |
146 | ||
147 | # Heartbleed | |
151 | 148 | heartbleed = parser.heart_bleed |
152 | 149 | |
153 | 150 | if heartbleed[0][0].attrib['isVulnerable'].lower() == 'true': |
154 | 151 | self.createAndAddVulnToService( |
155 | host_id, | |
156 | service_id, | |
152 | host_id, | |
153 | service_id, | |
157 | 154 | name="OpenSSL Heartbleed", |
158 | desc="OpenSSL Heartbleed is vulnerable", | |
155 | desc="OpenSSL Heartbleed is vulnerable", | |
159 | 156 | severity="critical") |
160 | ||
161 | #OpenSsl CCS Injection | |
157 | ||
158 | # OpenSsl CCS Injection | |
162 | 159 | openssl_ccs = parser.open_ssl_ccs |
163 | 160 | |
164 | 161 | if openssl_ccs[0][0].attrib['isVulnerable'].lower() == 'true': |
165 | 162 | self.createAndAddVulnToService( |
166 | host_id, | |
167 | service_id, | |
163 | host_id, | |
164 | service_id, | |
168 | 165 | name="OpenSSL CCS Injection", |
169 | desc="OpenSSL CCS Injection is vulnerable", | |
166 | desc="OpenSSL CCS Injection is vulnerable", | |
170 | 167 | severity="medium") |
171 | 168 | |
172 | def processCommandString(self, username, current_path, command_string): | |
173 | super().processCommandString(username, current_path, command_string) | |
174 | arg_match = self.xml_arg_re.match(command_string) | |
175 | if arg_match is None: | |
176 | return re.sub(r"(^.*?sslyze)", | |
177 | r"\1 --xml_out %s" % self._output_file_path, | |
178 | command_string) | |
179 | else: | |
180 | return re.sub(arg_match.group(1), | |
181 | r"--xml_out %s" % self._output_file_path, | |
182 | command_string) | |
183 | ||
184 | 169 | |
185 | 170 | def createPlugin(): |
186 | 171 | return SslyzePlugin() |
187 | ||
188 | # I'm Py3 |
0 | # I'm Py3⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | import re | |
7 | import json | |
8 | from faraday_plugins.plugins.plugin import PluginJsonFormat | |
9 | ||
10 | __author__ = "Blas Moyano" | |
11 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
12 | __credits__ = ["Blas Moyano"] | |
13 | __license__ = "" | |
14 | __version__ = "0.0.1" | |
15 | __maintainer__ = "Blas Moyano" | |
16 | __email__ = "[email protected]" | |
17 | __status__ = "Development" | |
18 | ||
19 | ||
20 | class SslyzeJsonParser: | |
21 | ||
22 | def __init__(self, json_output): | |
23 | json_sslyze = json.loads(json_output) | |
24 | scan_result = json_sslyze.get('server_scan_results') | |
25 | self.list_vul = self.get_vuln(scan_result) | |
26 | ||
27 | def get_vuln(self, scan_result): | |
28 | list_vuln = [] | |
29 | if scan_result: | |
30 | for scan in scan_result: | |
31 | try: | |
32 | host = self.get_host(scan['server_info']['server_location']) | |
33 | except KeyError: | |
34 | host = {} | |
35 | ||
36 | try: | |
37 | certif = self.get_certification(scan['scan_commands_results']['certificate_info']) | |
38 | except KeyError: | |
39 | certif = {} | |
40 | ||
41 | if not scan['scan_commands']: | |
42 | ciphers = {} | |
43 | else: | |
44 | commands = [] | |
45 | for command in scan['scan_commands']: | |
46 | if command.find("cipher") >= 0: | |
47 | commands.append(command) | |
48 | ciphers = self.get_cipher(scan['scan_commands_results'], commands) | |
49 | ||
50 | try: | |
51 | heartbleed = self.get_heartbleed(scan['scan_commands_results']['heartbleed']) | |
52 | except KeyError: | |
53 | heartbleed = {} | |
54 | ||
55 | try: | |
56 | openssl_ccs = self.get_openssl_ccs(scan['scan_commands_results']['openssl_ccs_injection']) | |
57 | except KeyError: | |
58 | openssl_ccs = {} | |
59 | ||
60 | json_vuln = { | |
61 | "host_info": host, | |
62 | "certification": certif, | |
63 | "ciphers": ciphers, | |
64 | "heartbleed": heartbleed, | |
65 | "openssl_ccs":openssl_ccs | |
66 | } | |
67 | ||
68 | list_vuln.append(json_vuln) | |
69 | return list_vuln | |
70 | ||
71 | def get_host(self, server_location): | |
72 | port = server_location.get('port', None) | |
73 | protocol = '' | |
74 | if port is not None: | |
75 | if port == 443: | |
76 | protocol = 'https' | |
77 | else: | |
78 | protocol = 'http' | |
79 | ||
80 | json_host = { | |
81 | "url": server_location.get('hostname', None), | |
82 | "ip": server_location.get('ip_address', '0.0.0.0'), | |
83 | "port": port, | |
84 | "protocol": protocol | |
85 | } | |
86 | ||
87 | return json_host | |
88 | ||
89 | def get_certification(self, certificate): | |
90 | certif_deploy = certificate['certificate_deployments'] | |
91 | send_certif = certif_deploy[0].get('leaf_certificate_subject_matches_hostname', True) | |
92 | ||
93 | if not send_certif: | |
94 | json_certif = { | |
95 | "name": "Certificate mismatch", | |
96 | "desc": f"Certificate does not match server hostname {certificate.get('hostname_used_for_server_name_indication', 'Not hostname')}", | |
97 | "severity": "info" | |
98 | } | |
99 | else: | |
100 | json_certif = {} | |
101 | return json_certif | |
102 | ||
103 | def get_cipher(self, scan_result, list_commands): | |
104 | weak_cipher_list = [ | |
105 | "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", | |
106 | "TLS_RSA_WITH_AES_128_CBC_SHA", | |
107 | "TLS_RSA_WITH_AES_128_CBC_SHA256", | |
108 | "TLS_RSA_WITH_AES_128_GCM_SHA256", | |
109 | "TLS_RSA_WITH_AES_256_CBC_SHA", | |
110 | "TLS_RSA_WITH_AES_256_CBC_SHA256", | |
111 | "TLS_RSA_WITH_AES_256_GCM_SHA384", | |
112 | "TLS_RSA_WITH_3DES_EDE_CBC_SHA", | |
113 | "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", | |
114 | "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" | |
115 | ] | |
116 | weak_cipher = {} | |
117 | for command in list_commands: | |
118 | weak_cipher[command] = [] | |
119 | try: | |
120 | if scan_result[command]['accepted_cipher_suites']: | |
121 | for cipher_suite in scan_result[command]['accepted_cipher_suites']: | |
122 | name_cipher = cipher_suite['cipher_suite'].get('name') | |
123 | if name_cipher in weak_cipher_list: | |
124 | if name_cipher not in weak_cipher[command]: | |
125 | weak_cipher[command].append(name_cipher) | |
126 | except KeyError: | |
127 | pass | |
128 | ||
129 | try: | |
130 | if scan_result[command]['cipher_suite_preferred_by_server'] is not None: | |
131 | for cipher_suite in scan_result[command]['accepted_cipher_suites']: | |
132 | name_cipher = cipher_suite['cipher_suite'].get('name') | |
133 | if name_cipher in weak_cipher_list: | |
134 | if name_cipher not in weak_cipher[command]: | |
135 | weak_cipher[command].append(name_cipher) | |
136 | except KeyError: | |
137 | pass | |
138 | ||
139 | return weak_cipher | |
140 | ||
141 | def get_heartbleed(self, heartbleed): | |
142 | json_heartbleed = {} | |
143 | if heartbleed.get('is_vulnerable_to_heartbleed', False): | |
144 | json_heartbleed = { | |
145 | "name": "OpenSSL Heartbleed", | |
146 | "desc": "OpenSSL Heartbleed is vulnerable", | |
147 | "severity": "critical" | |
148 | } | |
149 | return json_heartbleed | |
150 | ||
151 | def get_openssl_ccs(self, openssl_ccs): | |
152 | json_openssl_ccs = {} | |
153 | if openssl_ccs.get('is_vulnerable_to_ccs_injection', False): | |
154 | json_openssl_ccs = { | |
155 | "name": "OpenSSL CCS Injection", | |
156 | "desc": "OpenSSL CCS Injection is vulnerable", | |
157 | "severity": "medium" | |
158 | } | |
159 | return json_openssl_ccs | |
160 | ||
161 | ||
162 | class SslyzePlugin(PluginJsonFormat): | |
163 | ||
164 | def __init__(self): | |
165 | super().__init__() | |
166 | self.id = "Sslyze JSON" | |
167 | self.name = "Sslyze Json" | |
168 | self.plugin_version = "0.1" | |
169 | self.version = "3.4.5" | |
170 | self.json_keys = {'server_scan_results', 'sslyze_url'} | |
171 | self._command_regex = re.compile(r'^(sudo sslyze|sslyze|\.\/sslyze)\s+.*?') | |
172 | self.json_arg_re = re.compile(r"^.*(--json_out\s*[^\s]+).*$") | |
173 | ||
174 | def parseOutputString(self, output): | |
175 | parser = SslyzeJsonParser(output) | |
176 | ||
177 | for info_sslyze in parser.list_vul: | |
178 | info_sslyze['host_info'].get('url') | |
179 | host_id = self.createAndAddHost( | |
180 | info_sslyze['host_info'].get('ip'), | |
181 | os="unknown", | |
182 | hostnames=[ | |
183 | info_sslyze['host_info'].get('url') | |
184 | ] | |
185 | ) | |
186 | service_id = self.createAndAddServiceToHost( | |
187 | host_id, | |
188 | name=info_sslyze['host_info'].get('protocol'), | |
189 | protocol=info_sslyze['host_info'].get('protocol'), | |
190 | ports=[ | |
191 | info_sslyze['host_info'].get('port') | |
192 | ] | |
193 | ) | |
194 | ||
195 | if info_sslyze['certification']: | |
196 | self.createAndAddVulnToService( | |
197 | host_id, | |
198 | service_id, | |
199 | name=info_sslyze['certification'].get('name'), | |
200 | desc=info_sslyze['certification'].get('desc'), | |
201 | severity=info_sslyze['certification'].get('info')) | |
202 | ||
203 | if info_sslyze['ciphers']: | |
204 | for k, v in info_sslyze['ciphers'].items(): | |
205 | if len(v) != 0: | |
206 | for ciphers in v: | |
207 | key = k.replace('_cipher_suites', '') | |
208 | self.createAndAddVulnToService( | |
209 | host_id, | |
210 | service_id, | |
211 | name=ciphers, | |
212 | desc=f"In protocol [{key}], weak cipher suite: {ciphers}", | |
213 | severity="low") | |
214 | ||
215 | if info_sslyze['heartbleed']: | |
216 | self.createAndAddVulnToService( | |
217 | host_id, | |
218 | service_id, | |
219 | name=info_sslyze['heartbleed'].get('name'), | |
220 | desc=info_sslyze['heartbleed'].get('desc'), | |
221 | severity=info_sslyze['heartbleed'].get('severity')) | |
222 | ||
223 | if info_sslyze['openssl_ccs']: | |
224 | self.createAndAddVulnToService( | |
225 | host_id, | |
226 | service_id, | |
227 | name=info_sslyze['openssl_ccs'].get('name'), | |
228 | desc=info_sslyze['openssl_ccs'].get('desc'), | |
229 | severity=info_sslyze['openssl_ccs'].get('severity')) | |
230 | ||
231 | def processCommandString(self, username, current_path, command_string): | |
232 | super().processCommandString(username, current_path, command_string) | |
233 | arg_match = self.json_arg_re.match(command_string) | |
234 | if arg_match is None: | |
235 | return re.sub(r"(^.*?sslyze)", | |
236 | r"\1 --json_out %s" % self._output_file_path, | |
237 | command_string) | |
238 | else: | |
239 | return re.sub(arg_match.group(1), | |
240 | r"--json_out %s" % self._output_file_path, | |
241 | command_string) | |
242 | ||
243 | ||
244 | def createPlugin(): | |
245 | return SslyzePlugin() | |
246 |
55 | 55 | |
56 | 56 | |
57 | 57 | |
58 | def parseOutputString(self, output, debug=False): | |
58 | def parseOutputString(self, output): | |
59 | 59 | |
60 | 60 | host_info = re.search(r"Connected to (.+)\.", output) |
61 | 61 | |
63 | 63 | ip_address = resolve_hostname(hostname) |
64 | 64 | |
65 | 65 | if host_info is not None: |
66 | h_id = self.createAndAddHost(ip_address) | |
67 | i_id = self.createAndAddInterface( | |
68 | h_id, ip_address, ipv4_address=ip_address, hostname_resolution=[hostname]) | |
69 | s_id = self.createAndAddServiceToInterface(h_id, i_id, self._port, | |
70 | "tcp", | |
71 | ports=[self._port], | |
72 | status="open") | |
66 | h_id = self.createAndAddHost(ip_address, hostnames=[hostname]) | |
67 | s_id = self.createAndAddServiceToHost(h_id, self._port, "tcp", ports=[self._port], status="open") | |
73 | 68 | return True |
74 | 69 | |
75 | 70 | def processCommandString(self, username, current_path, command_string): |
99 | 99 | } |
100 | 100 | |
101 | 101 | |
102 | def parseOutputString(self, output, debug=False): | |
102 | def parseOutputString(self, output): | |
103 | 103 | """ |
104 | 104 | This method will discard the output the shell sends, it will read it from |
105 | 105 | the xml where it expects it to be present. |
106 | ||
107 | NOTE: if 'debug' is true then it is being run from a test case and the | |
108 | output being sent is valid. | |
109 | 106 | """ |
110 | ||
111 | print("este es el output (%s)" % output) | |
112 | ||
113 | if debug: | |
114 | parser = TheharvesterParser(output) | |
115 | else: | |
116 | ||
117 | parser = TheharvesterParser(output) | |
118 | ||
119 | print(len(parser.items)) | |
120 | for item in parser.items: | |
121 | host = [] | |
122 | if item['host'] != item['ip']: | |
123 | host = [item['host']] | |
124 | h_id = self.createAndAddHost(item['ip']) | |
125 | i_id = self.createAndAddInterface(h_id, item['ip'], ipv4_address=item[ | |
126 | 'ip'], hostname_resolution=host) | |
127 | ||
107 | parser = TheharvesterParser(output) | |
108 | for item in parser.items: | |
109 | host = [] | |
110 | if item['host'] != item['ip']: | |
111 | host = [item['host']] | |
112 | h_id = self.createAndAddHost(item['ip'], hostnames=host) | |
128 | 113 | del parser |
129 | 114 | |
130 | 115 |
23 | 23 | self.command_string = "" |
24 | 24 | self._command_regex = re.compile(r'^(traceroute|traceroute6)\s+.*?') |
25 | 25 | |
26 | def parseOutputString(self, output, debug=False): | |
26 | def parseOutputString(self, output): | |
27 | 27 | |
28 | 28 | print("[*]Parsing Output...") |
29 | 29 |
224 | 224 | "-h": "Display this help message.", |
225 | 225 | } |
226 | 226 | |
227 | def parseOutputString(self, output, debug=False): | |
227 | def parseOutputString(self, output): | |
228 | 228 | |
229 | 229 | parser = W3afXmlParser(output) |
230 | 230 | ip = resolve_hostname(parser.host) |
231 | h_id = self.createAndAddHost(ip) | |
232 | i_id = self.createAndAddInterface(h_id, ip, ipv4_address=ip, hostname_resolution=[parser.host]) | |
233 | s_id = self.createAndAddServiceToInterface(h_id, i_id, "http", "tcp", ports=[parser.port], status="open") | |
231 | h_id = self.createAndAddHost(ip, hostnames=[parser.host]) | |
232 | s_id = self.createAndAddServiceToHost(h_id, "http", "tcp", ports=[parser.port], status="open") | |
234 | 233 | |
235 | 234 | for item in parser.items: |
236 | 235 | v_id = self.createAndAddVulnWebToService(h_id, s_id, item.name, |
240 | 239 | del parser |
241 | 240 | |
242 | 241 | |
243 | def setHost(self): | |
244 | pass | |
245 | ||
246 | 242 | |
247 | 243 | def createPlugin(): |
248 | 244 | return W3afPlugin() |
104 | 104 | host = parser.scaninfo[file]['host'] |
105 | 105 | port = parser.scaninfo[file]['port'] |
106 | 106 | h_id = self.createAndAddHost(host) |
107 | if(re.match("(^[2][0-5][0-5]|^[1]{0,1}[0-9]{1,2})\.([0-2][0-5][0-5]|[1]{0,1}[0-9]{1,2})\.([0-2][0-5][0-5]|[1]{0,1}[0-9]{1,2})\.([0-2][0-5][0-5]|[1]{0,1}[0-9]{1,2})$", host)): | |
108 | i_id = self.createAndAddInterface(h_id, | |
109 | host, | |
110 | ipv4_address=host) | |
111 | else: | |
112 | i_id = self.createAndAddInterface(h_id, | |
113 | host, | |
114 | ipv6_address=host) | |
115 | ||
116 | s_id = self.createAndAddServiceToInterface( | |
117 | h_id, i_id, "http", protocol="tcp", ports=port) | |
107 | s_id = self.createAndAddServiceToHost(h_id, "http", protocol="tcp", ports=port) | |
118 | 108 | for vuln in parser.result[file]: |
119 | 109 | if parser.scaninfo[file]['type'] == "phpini": |
120 | 110 | vuln_name = f"{parser.scaninfo[file]['file']}: {vuln}" |
97 | 97 | |
98 | 98 | self._output_path = None |
99 | 99 | |
100 | def parseOutputString(self, output, debug=False): | |
100 | def parseOutputString(self, output): | |
101 | 101 | """ |
102 | 102 | This method will discard the output the shell sends, it will read it from |
103 | 103 | the xml where it expects it to be present. |
104 | ||
105 | NOTE: if 'debug' is true then it is being run from a test case and the | |
106 | output being sent is valid. | |
107 | 104 | """ |
108 | 105 | |
109 | 106 | if self._output_path is None: |
113 | 110 | return False |
114 | 111 | |
115 | 112 | parser = WebfuzzerParser(self._output_path) |
116 | ||
117 | h_id = self.createAndAddHost(parser.ipaddress) | |
118 | ||
119 | i_id = self.createAndAddInterface( | |
120 | h_id, parser.ipaddress, ipv4_address=parser.ipaddress, hostname_resolution=[parser.hostname]) | |
121 | ||
113 | h_id = self.createAndAddHost(parser.ipaddress, hostnames=[parser.hostname]) | |
122 | 114 | first = True |
123 | 115 | for item in parser.items: |
124 | 116 | if first: |
125 | s_id = self.createAndAddServiceToInterface(h_id, i_id, parser.port, | |
126 | "tcp", | |
127 | ports=[parser.port]) | |
117 | s_id = self.createAndAddServiceToHost(h_id, parser.port, "tcp", ports=[parser.port]) | |
128 | 118 | first = False |
129 | ||
130 | v_id = self.createAndAddVulnWebToService(h_id, s_id, name=item['desc'], | |
131 | path=item['url'], response=item[ | |
132 | 'resp'], | |
133 | method=item['method'], website=parser.hostname) | |
119 | v_id = self.createAndAddVulnWebToService(h_id, s_id, name=item['desc'],path=item['url'], | |
120 | response=item['resp'], method=item['method'], | |
121 | website=parser.hostname) | |
134 | 122 | |
135 | 123 | del parser |
136 | 124 |
131 | 131 | |
132 | 132 | for vuln in vulns: |
133 | 133 | |
134 | host_id = self.createAndAddHost( | |
135 | vuln.get("Host").get("name")) | |
136 | ||
137 | interface_id = self.createAndAddInterface( | |
138 | host_id, vuln.get("Host").get("name")) | |
139 | ||
140 | service_id = self.createAndAddServiceToInterface( | |
141 | host_id, interface_id, | |
142 | vuln.get("Service").get("name"), | |
143 | protocol=vuln.get("Service").get("name"), | |
144 | ports=[vuln.get("Service").get("port")]) | |
134 | host_id = self.createAndAddHost(vuln.get("Host").get("name")) | |
135 | service_id = self.createAndAddServiceToHost(host_id, vuln.get("Service").get("name"), | |
136 | protocol=vuln.get("Service").get("name"), | |
137 | ports=[vuln.get("Service").get("port")]) | |
145 | 138 | |
146 | 139 | self.createAndAddVulnWebToService( |
147 | 140 | host_id, service_id, |
51 | 51 | |
52 | 52 | return data |
53 | 53 | |
54 | def parseOutputString(self, output, debug=False): | |
54 | def parseOutputString(self, output): | |
55 | 55 | output_list = output.split('\n') |
56 | 56 | info = self.parseData(output_list) |
57 | 57 |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2020 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | import json | |
7 | from faraday_plugins.plugins.plugin import PluginJsonFormat | |
8 | ||
9 | __author__ = "Blas Moyano" | |
10 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
11 | __credits__ = ["Blas Moyano"] | |
12 | __license__ = "" | |
13 | __version__ = "0.0.1" | |
14 | __maintainer__ = "Blas Moyano" | |
15 | __email__ = "[email protected]" | |
16 | __status__ = "Development" | |
17 | ||
18 | ||
19 | class WhatWebJsonParser: | |
20 | ||
21 | def __init__(self, json_output): | |
22 | list_data = json.loads(json_output) | |
23 | self.host_whatweb = [] | |
24 | for info in list_data: | |
25 | try: | |
26 | server_info = info['plugins']['HTTPServer'] | |
27 | except KeyError: | |
28 | server_info = {} | |
29 | ||
30 | try: | |
31 | ip_info = info['plugins']['IP'] | |
32 | except KeyError: | |
33 | ip_info = {} | |
34 | ||
35 | try: | |
36 | country_info = info['plugins']['Country'] | |
37 | except KeyError: | |
38 | country_info = {} | |
39 | ||
40 | whatweb_data = { | |
41 | "url": info.get('target', None), | |
42 | "os": None if not server_info else server_info.get('os', None), | |
43 | "os_detail": "Unknown" if not server_info else server_info.get('string', 'Unknown'), | |
44 | "ip": ['0.0.0.0'] if ip_info is None else ip_info.get('string', None), | |
45 | "country": "" if country_info is None else country_info.get('string', "") | |
46 | } | |
47 | self.host_whatweb.append(whatweb_data) | |
48 | ||
49 | ||
50 | class WhatWebPlugin(PluginJsonFormat): | |
51 | ||
52 | def __init__(self): | |
53 | super().__init__() | |
54 | self.id = "whatweb" | |
55 | self.name = "WhatWebPlugin" | |
56 | self.plugin_version = "0.1" | |
57 | self.version = "0.0.1" | |
58 | self.json_keys = {'target', 'http_status', 'plugins'} | |
59 | ||
60 | ||
61 | def parseOutputString(self, output): | |
62 | parser = WhatWebJsonParser(output) | |
63 | for whatweb_data in parser.host_whatweb: | |
64 | desc = f"{whatweb_data['os_detail']} - {whatweb_data['country']}" | |
65 | if whatweb_data['os'] is None: | |
66 | datail_os = "Unknown" | |
67 | else: | |
68 | datail_os = whatweb_data['os'][0] | |
69 | ||
70 | self.createAndAddHost(whatweb_data['ip'][0], | |
71 | os=datail_os, | |
72 | hostnames=whatweb_data['url'], | |
73 | description=desc) | |
74 | ||
75 | ||
76 | def createPlugin(): | |
77 | return WhatWebPlugin() |
29 | 29 | self.version = "3.4.5" |
30 | 30 | self.json_keys = {"vulnerabilities"} |
31 | 31 | |
32 | def parseOutputString(self, output, debug=False): | |
32 | def parseOutputString(self, output): | |
33 | 33 | parser = json.loads(output) |
34 | 34 | if parser.get('vulnerabilities'): |
35 | 35 | for vulnerability in parser['vulnerabilities']: |
67 | 67 | "--version": "output version information and exit", |
68 | 68 | } |
69 | 69 | |
70 | def processCommandString(self, username, current_path, command_string): | |
71 | self.command_string = command_string | |
72 | super(CmdWhoisPlugin, self).processCommandString(username, current_path, command_string) | |
70 | 73 | |
74 | def parseOutputString(self, output): | |
75 | matches = re.findall("Name Server:\s*(.*)\s*", output) | |
76 | if not matches: | |
77 | matches = re.findall("nserver:\s*(.*)\s*", output) | |
71 | 78 | |
72 | def parseOutputString(self, output, debug=False): | |
73 | matches = re.findall("Name Server:\s*(.*)\s*", output) | |
74 | for m in matches: | |
75 | m = m.strip() | |
76 | ip = resolve_hostname(m) | |
77 | h_id = self.createAndAddHost(ip, "os unknown") | |
78 | i_id = self.createAndAddInterface( | |
79 | h_id, ip, "00:00:00:00:00:00", ip, hostname_resolution=[m]) | |
79 | if not matches: | |
80 | ip = re.findall(r'[0-9]+(?:\.[0-9]+){3}', self.command_string) | |
81 | if not ip: | |
82 | url = self.command_string.replace('whois ', '') | |
83 | ip = [resolve_hostname(url)] | |
84 | matches_descr = re.findall("descr:\s*(.*)\s*", output) | |
85 | ||
86 | matches_netname = re.findall("NetName:\s*(.*)\s*", output) | |
87 | if not matches_netname: | |
88 | matches_netname = re.findall("netname:\s*(.*)\s*", output) | |
89 | matches_ref = re.findall("Ref:\s*(.*)\s*", output) | |
90 | desc = "" | |
91 | ref = [] | |
92 | os_name = "unknown" | |
93 | ||
94 | for md in matches_descr: | |
95 | desc = md.strip() | |
96 | ||
97 | for mr in matches_ref: | |
98 | ref.append(mr.strip()) | |
99 | ||
100 | for osname in matches_netname: | |
101 | os_name = osname.strip() | |
102 | self.createAndAddHost( | |
103 | ip[0], | |
104 | os_name, | |
105 | hostnames=[ref], | |
106 | description=desc | |
107 | ) | |
108 | else: | |
109 | for m in matches: | |
110 | m = m.strip() | |
111 | url = re.findall(r'https?://[^\s<>"]+|.[^\s<>"]+', str(m)) | |
112 | ip = resolve_hostname(url[0]) | |
113 | self.createAndAddHost( | |
114 | ip, | |
115 | hostnames=[m] | |
116 | ) | |
117 | matches_domain = re.findall("Domain Name:\s*(.*)\s*", output) | |
118 | for md in matches_domain: | |
119 | md = md.strip() | |
120 | ip = resolve_hostname(md) | |
121 | self.createAndAddHost( | |
122 | ip, | |
123 | hostnames=[md] | |
124 | ) | |
80 | 125 | return True |
81 | ||
82 | 126 | |
83 | 127 | |
84 | 128 | def createPlugin(): |
85 | 129 | return CmdWhoisPlugin() |
86 | ||
87 | # I'm Py3 |
56 | 56 | self.version = "3.4.5" |
57 | 57 | self.json_keys = {"target_url", "effective_url", "interesting_findings"} |
58 | 58 | |
59 | def parseOutputString(self, output, debug=False): | |
59 | def parseOutputString(self, output): | |
60 | 60 | parser = WPScanJsonParser(output) |
61 | 61 | url_data = parser.parse_url(parser.json_data['target_url']) |
62 | 62 | host_id = self.createAndAddHost(url_data['address'], hostnames=[url_data['hostname']]) |
168 | 168 | |
169 | 169 | |
170 | 170 | |
171 | def parseOutputString(self, output, debug=False): | |
171 | def parseOutputString(self, output): | |
172 | 172 | |
173 | 173 | parser = X1XmlParser(output) |
174 | 174 | for item in parser.items: |
175 | h_id = self.createAndAddHost(item.host, item.name) | |
176 | i_id = self.createAndAddInterface( | |
177 | h_id, item.host, ipv4_address=item.host, hostname_resolution=[item.vclass]) | |
178 | s_id = self.createAndAddServiceToInterface(h_id, i_id, item.srvname, | |
179 | item.protocol, | |
180 | ports=[str(item.port)], | |
181 | status="open") | |
175 | h_id = self.createAndAddHost(item.host, item.name, hostnames=[item.vclass]) | |
176 | s_id = self.createAndAddServiceToHost(h_id, item.srvname, item.protocol, ports=[str(item.port)], | |
177 | status="open") | |
182 | 178 | for v in item.results: |
183 | 179 | desc = v.description |
184 | 180 | v_id = self.createAndAddVulnToService(h_id, s_id, v.name, desc=desc, |
25 | 25 | self._command_regex = re.compile(r'^(sudo xsssniper|xsssniper|sudo xsssniper\.py|xsssniper\.py|sudo python' |
26 | 26 | r'xsssniper\.py|.\/xsssniper\.py|python xsssniper\.py)\s+') |
27 | 27 | |
28 | def parseOutputString(self, output, debug=False): | |
28 | def parseOutputString(self, output): | |
29 | 29 | parametro = [] |
30 | 30 | lineas = output.split("\n") |
31 | 31 | aux = 0 |
35 | 35 | linea = linea.lower() |
36 | 36 | if ((linea.find("target:")>0)): |
37 | 37 | url = re.findall('(?:[-\w.]|(?:%[\da-fA-F]{2}))+', linea) |
38 | print(url) | |
39 | host_id = self.createAndAddHost(url[3]) | |
40 | 38 | address = resolve_hostname(url[3]) |
41 | interface_id = self.createAndAddInterface(host_id,address,ipv4_address=address,hostname_resolution=url[3]) | |
39 | host_id = self.createAndAddHost(address, hostnames=url[3]) | |
42 | 40 | if ((linea.find("method")>0)): |
43 | 41 | list_a = re.findall("\w+", linea) |
44 | 42 | metodo= list_a[1] |
48 | 46 | if ((linea.find("param:")>0)): |
49 | 47 | list2 = re.findall("\w+",linea) |
50 | 48 | parametro.append(list2[1]) |
51 | service_id = self.createAndAddServiceToInterface(host_id, interface_id, self.protocol, 'tcp', | |
52 | ports=['80'], status='Open', version="", description="") | |
49 | service_id = self.createAndAddServiceToHost(host_id, self.protocol, 'tcp', ports=['80'], status='Open', | |
50 | version="", description="") | |
53 | 51 | if aux != 0: |
54 | 52 | self.createAndAddVulnWebToService(host_id, service_id, name="xss", desc="XSS", ref='', severity='med', |
55 | 53 | website=url[0], path='', method=metodo, pname='', |
182 | 182 | |
183 | 183 | for elem in arr: |
184 | 184 | uri = elem.find('uri').text |
185 | self.parse_uri(uri) | |
186 | ||
187 | self.requests = "\n".join([i['uri'] for i in self.items]) | |
188 | ||
189 | def parse_uri(self, uri): | |
190 | ||
191 | url_parse = urlparse(uri) | |
192 | protocol = url_parse.scheme | |
193 | host = url_parse.netloc | |
194 | port = url_parse.port | |
185 | method_element = elem.find('method') | |
186 | if method_element: | |
187 | method = elem.find('method').text | |
188 | else: | |
189 | method = "" | |
190 | self.parse_uri(uri, method) | |
191 | ||
192 | def parse_uri(self, uri, method): | |
193 | ||
194 | parsed_url = urlparse(uri) | |
195 | protocol = parsed_url.scheme | |
196 | host = parsed_url.netloc | |
197 | port = parsed_url.port | |
195 | 198 | |
196 | 199 | try: |
197 | 200 | params = [i.split('=')[0] |
203 | 206 | 'uri': uri, |
204 | 207 | 'params': ', '.join(params), |
205 | 208 | 'host': host, |
209 | 'website': f"{protocol}://{host}", | |
206 | 210 | 'protocol': protocol, |
207 | 'port': port | |
211 | 'port': port, | |
212 | 'method': method, | |
213 | 'path': parsed_url.path, | |
214 | 'query': parsed_url.query | |
208 | 215 | } |
209 | 216 | self.items.append(item) |
210 | 217 | |
237 | 244 | self.options = None |
238 | 245 | |
239 | 246 | |
240 | def parseOutputString(self, output, debug=False): | |
247 | def parseOutputString(self, output): | |
241 | 248 | """ |
242 | 249 | This method will discard the output the shell sends, it will read it |
243 | 250 | from the xml where it expects it to be present. |
244 | ||
245 | NOTE: if 'debug' is true then it is being run from a test case and the | |
246 | output being sent is valid. | |
247 | 251 | """ |
248 | 252 | |
249 | 253 | parser = ZapXmlParser(output) |
254 | 258 | if site.host != site.ip: |
255 | 259 | host = [site.host] |
256 | 260 | |
257 | h_id = self.createAndAddHost(site.ip) | |
258 | ||
259 | i_id = self.createAndAddInterface( | |
260 | h_id, | |
261 | site.ip, | |
262 | ipv4_address=site.ip, | |
263 | hostname_resolution=host | |
264 | ) | |
265 | ||
266 | s_id = self.createAndAddServiceToInterface( | |
267 | h_id, | |
268 | i_id, | |
269 | "http", | |
270 | "tcp", | |
271 | ports=[site.port], | |
272 | status='open' | |
273 | ) | |
261 | h_id = self.createAndAddHost(site.ip, hostnames=host) | |
262 | ||
263 | s_id = self.createAndAddServiceToHost(h_id, "http", "tcp", ports=[site.port], status='open') | |
274 | 264 | |
275 | 265 | for item in site.items: |
276 | self.createAndAddVulnWebToService( | |
277 | h_id, | |
278 | s_id, | |
279 | item.name, | |
280 | item.desc, | |
281 | website=site.host, | |
282 | severity=item.severity, | |
283 | path=item.items[0]['uri'], | |
284 | params=item.items[0]['params'], | |
285 | request=item.requests, | |
286 | ref=item.ref, | |
287 | resolution=item.resolution | |
288 | ) | |
266 | for instance in item.items: | |
267 | self.createAndAddVulnWebToService( | |
268 | h_id, | |
269 | s_id, | |
270 | item.name, | |
271 | item.desc, | |
272 | website=instance['website'], | |
273 | query=instance['query'], | |
274 | severity=item.severity, | |
275 | path=instance['path'], | |
276 | params=instance['params'], | |
277 | method=instance['method'], | |
278 | ref=item.ref, | |
279 | resolution=item.resolution | |
280 | ) | |
289 | 281 | |
290 | 282 | del parser |
291 | 283 |
36 | 36 | |
37 | 37 | @click.command() |
38 | 38 | @click.option('--force', is_flag=True) |
39 | def generate_reports_tests(force): | |
39 | @click.option('--debug', is_flag=False) | |
40 | def generate_reports_tests(force, debug): | |
40 | 41 | generated_summaries = 0 |
41 | 42 | analysed_reports = 0 |
42 | 43 | click.echo(f"{colorama.Fore.GREEN}Generate Faraday Plugins Tests Summary") |
43 | 44 | plugins_manager = PluginsManager() |
44 | 45 | analyzer = ReportAnalyzer(plugins_manager) |
45 | 46 | for report_file_path in list_report_files(): |
47 | if debug: | |
48 | click.echo(f"File: {report_file_path}") | |
46 | 49 | plugin: PluginBase = analyzer.get_plugin(report_file_path) |
47 | 50 | if not plugin: |
48 | 51 | click.echo(f"{colorama.Fore.YELLOW}Plugin for file: ({report_file_path}) not found") |
0 | import json | |
1 | import os | |
2 | import re | |
3 | from tempfile import NamedTemporaryFile | |
4 | from click.testing import CliRunner | |
5 | from faraday_plugins.commands import list_plugins, detect_command, process_command, detect_report, process_report | |
6 | ||
7 | ||
8 | def test_list_plugins(): | |
9 | runner = CliRunner() | |
10 | result = runner.invoke(list_plugins) | |
11 | assert result.exit_code == 0 | |
12 | loaded_plugins = re.search(r'Loaded Plugins: (?P<loaded_plugins>\d+)', result.output) | |
13 | assert loaded_plugins | |
14 | assert int(loaded_plugins.groupdict().get('loaded_plugins', 0)) > 0 | |
15 | ||
16 | ||
17 | def test_detect_invalid_command(): | |
18 | runner = CliRunner() | |
19 | result = runner.invoke(detect_command, args=['invalid_command']) | |
20 | assert result.exit_code == 0 | |
21 | assert result.output.strip() == "Failed to detect command: invalid_command" | |
22 | ||
23 | ||
24 | def test_detect_command(): | |
25 | runner = CliRunner() | |
26 | result = runner.invoke(detect_command, args=['ping -c 1 www.google.com']) | |
27 | assert result.exit_code == 0 | |
28 | assert result.output.strip() == "Faraday Plugin: ping" | |
29 | ||
30 | ||
31 | def test_process_command(): | |
32 | runner = CliRunner() | |
33 | result = runner.invoke(process_command, args=['ping -c 1 www.google.com', '--summary']) | |
34 | assert result.exit_code == 0 | |
35 | summary = json.loads(result.output.strip()) | |
36 | assert summary['hosts'] == 1 | |
37 | ||
38 | ||
39 | def test_process_command_to_file(): | |
40 | runner = CliRunner() | |
41 | with runner.isolated_filesystem() as file_system: | |
42 | output_file = os.path.join(file_system, "test.json") | |
43 | result = runner.invoke(process_command, args=['ping -c 1 www.google.com', '-o', output_file]) | |
44 | assert result.exit_code == 0 | |
45 | assert os.path.isfile(output_file) | |
46 | with open(output_file) as f: | |
47 | vuln_json = json.load(f) | |
48 | assert len(vuln_json['hosts']) == 1 | |
49 | ||
50 | ||
51 | def test_detect_report(): | |
52 | report_file = os.path.join('./report-collection', 'faraday_plugins_tests', 'Nmap', 'nmap_5.21.xml') | |
53 | runner = CliRunner() | |
54 | result = runner.invoke(detect_report, args=[report_file]) | |
55 | assert result.exit_code == 0 | |
56 | assert "Faraday Plugin: Nmap" == result.output.strip() | |
57 | ||
58 | ||
59 | def test_detect_report_dont_exists(): | |
60 | report_file = os.path.join('./report-collection', 'faraday_plugins_tests', 'Nmap', 'invalid_report.xml') | |
61 | runner = CliRunner() | |
62 | result = runner.invoke(detect_report, args=[report_file]) | |
63 | assert result.exit_code == 0 | |
64 | assert "Don't Exists" in result.output.strip() | |
65 | ||
66 | ||
67 | def test_process_report_summary(): | |
68 | report_file = os.path.join('./report-collection', 'faraday_plugins_tests', 'Nmap', 'nmap_5.21.xml') | |
69 | summary_file = os.path.join('./report-collection', 'faraday_plugins_tests', 'Nmap', 'nmap_5.21_summary.json') | |
70 | runner = CliRunner() | |
71 | result = runner.invoke(process_report, args=[report_file, '--summary']) | |
72 | assert result.exit_code == 0 | |
73 | summary = json.loads(result.output.strip()) | |
74 | with open(summary_file) as f: | |
75 | saved_summary = json.load(f) | |
76 | vuln_hashes = set(summary['vuln_hashes']) | |
77 | saved_vuln_hashes = set(saved_summary.get('vuln_hashes', [])) | |
78 | assert summary['hosts'] == saved_summary['hosts'] | |
79 | assert summary['services'] == saved_summary['services'] | |
80 | assert summary['hosts_vulns'] == saved_summary['hosts_vulns'] | |
81 | assert summary['services_vulns'] == saved_summary['services_vulns'] | |
82 | assert summary['severity_vulns'] == saved_summary['severity_vulns'] | |
83 | assert vuln_hashes == saved_vuln_hashes | |
84 |