New upstream version 1.2
Sophie Brun
3 years ago
0 | 0 | stages: |
1 | 1 | - pre_testing |
2 | 2 | - testing |
3 | - post_testing | |
3 | 4 | - publish |
4 | 5 | |
5 | 6 | before_script: |
6 | 7 | - apt-get update -qy |
7 | 8 | - pip install pip -U |
9 | ||
10 | workflow: | |
11 | rules: | |
12 | - if: $CI_MERGE_REQUEST_ID | |
13 | when: never | |
14 | - when: always | |
8 | 15 | |
9 | 16 | flake8: |
10 | 17 | image: python:3 |
20 | 27 | - wc -l files.processed |
21 | 28 | |
22 | 29 | tests: |
23 | image: python:3 | |
30 | image: python:3.7 | |
24 | 31 | stage: testing |
25 | 32 | coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' |
26 | 33 | before_script: |
28 | 35 | - virtualenv -p python3 faraday_venv |
29 | 36 | - source faraday_venv/bin/activate |
30 | 37 | - pip3 install pytest pytest-xdist pytest-cov |
31 | - mkdir run_from | |
32 | - cd run_from && git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/faradaysec/support/report-collection.git | |
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 .. | |
33 | 44 | script: |
34 | - cd - | |
35 | 45 | - source faraday_venv/bin/activate |
36 | 46 | - python3 setup.py install |
37 | - cd run_from && pytest ../tests --capture=sys -v --cov=faraday_plugins --color=yes --disable-warnings | |
47 | - pytest tests --capture=sys -v --cov=faraday_plugins --color=yes --disable-warnings | |
38 | 48 | |
39 | publish_pipy: | |
49 | ||
50 | 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 | |
73 | ||
74 | publish_pypi: | |
40 | 75 | image: python:3 |
41 | 76 | stage: publish |
42 | before_script: | |
43 | - pip3 install virtualenv | |
44 | - virtualenv -p python3 faraday_venv twine | |
45 | - source faraday_venv/bin/activate | |
46 | 77 | script: |
78 | - apt-get update -qy | |
79 | - apt-get install twine -y | |
47 | 80 | - python setup.py sdist bdist_wheel |
48 | - twine upload dist/* -u $TWINE_USERNAME -p $TWINE_PASSWORD | |
81 | - twine upload -u $PYPI_USER -p $PYPI_PASS dist/* --verbose | |
82 | rules: | |
83 | - if: '$CI_COMMIT_TAG' | |
84 | when: on_success | |
49 | 85 | |
50 | only: | |
51 | variables: | |
52 | - $CI_COMMIT_TAG =~ /^v[0-9.]+$/ |
8 | 8 | > List Plugins |
9 | 9 | |
10 | 10 | ```shell script |
11 | python -m faraday_plugins list | |
11 | faraday-plugins show | |
12 | ``` | |
13 | ||
14 | > Test autodetect plugin from command | |
15 | ||
16 | ```shell script | |
17 | faraday-plugins detect-command "ping -c 4 www.google.com" | |
18 | > Faraday Plugin: ping | |
19 | ``` | |
20 | ||
21 | > Test command with plugin | |
22 | ||
23 | Optional params: | |
24 | ||
25 | - -dr: Dont run, just show the generated command | |
26 | ||
27 | ```shell script | |
28 | 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 | { | |
43 | "hosts": [ | |
44 | { | |
45 | "ip": "216.58.222.36", | |
46 | "os": "unknown", | |
47 | "hostnames": [ | |
48 | "www.google.com" | |
49 | ], | |
50 | "description": "", | |
51 | "mac": "00:00:00:00:00:00", | |
52 | "credentials": [], | |
53 | "services": [], | |
54 | "vulnerabilities": [] | |
55 | } | |
56 | ], | |
57 | "command": { | |
58 | "tool": "ping", | |
59 | "command": "ping", | |
60 | "params": "-c4 www.google.com", | |
61 | "user": "aenima", | |
62 | "hostname": "", | |
63 | "start_date": "2020-05-05T23:09:39.656132", | |
64 | "duration": 56789, | |
65 | "import_source": "report" | |
66 | } | |
67 | } | |
12 | 68 | ``` |
13 | 69 | |
14 | 70 | > Test autodetect plugin from report |
15 | 71 | |
16 | 72 | ```shell script |
17 | python -m faraday_plugins detect /path/to/report.xml | |
73 | faraday-plugins detect-report /path/to/report.xml | |
18 | 74 | ``` |
19 | 75 | |
20 | 76 | |
21 | 77 | > Test report with plugin |
22 | 78 | |
23 | 79 | ```shell script |
24 | python -m faraday_plugins process appscan /path/to/report.xml | |
80 | faraday-plugins process-report /path/to/report.xml | |
25 | 81 | ``` |
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 | |
26 | 92 | |
27 | 93 | > Plugin Logger |
28 | 94 | |
30 | 96 | |
31 | 97 | ```shell script |
32 | 98 | export PLUGIN_DEBUG=1 |
33 | python -m faraday_plugins process appscan /path/to/report.xml | |
99 | faraday-plugins proces-report /path/to/report.xml | |
34 | 100 | 2019-11-15 20:37:03,355 - faraday.faraday_plugins.plugins.manager - INFO [manager.py:113 - _load_plugins()] Loading Native Plugins... |
35 | 101 | 2019-11-15 20:37:03,465 - faraday.faraday_plugins.plugins.manager - DEBUG [manager.py:123 - _load_plugins()] Load Plugin [acunetix] |
36 | 102 | 2019-11-15 20:37:03,495 - faraday.faraday_plugins.plugins.manager - DEBUG [manager.py:123 - _load_plugins()] Load Plugin [amap] |
44 | 110 | ... |
45 | 111 | ``` |
46 | 112 | |
113 | ||
114 | > More documentation here https://github.com/infobyte/faraday/wiki/Basic-plugin-development |
0 | import logging | |
1 | import os | |
2 | import sys | |
3 | import click | |
4 | ||
5 | from faraday_plugins.plugins.manager import PluginsManager, ReportAnalyzer | |
6 | ||
7 | root_logger = logging.getLogger("faraday") | |
8 | if not root_logger.handlers: | |
9 | PLUGIN_DEBUG = os.environ.get("PLUGIN_DEBUG", "0") | |
10 | if PLUGIN_DEBUG == "1": | |
11 | out_hdlr = logging.StreamHandler(sys.stdout) | |
12 | out_hdlr.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s [%(filename)s:%(lineno)s - %(funcName)s()] %(message)s')) | |
13 | out_hdlr.setLevel(logging.DEBUG) | |
14 | root_logger.addHandler(out_hdlr) | |
15 | root_logger.setLevel(logging.DEBUG) | |
16 | ||
17 | ||
18 | @click.group() | |
19 | def cli(): | |
20 | pass | |
21 | ||
22 | ||
23 | @cli.command() | |
24 | def list(): | |
25 | plugins_manager = PluginsManager() | |
26 | click.echo("Available Plugins:") | |
27 | loaded_plugins = 0 | |
28 | for plugin_id, plugin in plugins_manager.get_plugins(): | |
29 | click.echo(f"{plugin.id} - {plugin.name}") | |
30 | loaded_plugins += 1 | |
31 | click.echo(f"Loaded Plugins: {loaded_plugins}") | |
32 | ||
33 | ||
34 | @cli.command() | |
35 | @click.argument('plugin_id') | |
36 | @click.argument('report_file') | |
37 | def process(plugin_id, report_file): | |
38 | if not os.path.isfile(report_file): | |
39 | click.echo(f"File {report_file} Don't Exists") | |
40 | else: | |
41 | plugins_manager = PluginsManager() | |
42 | plugin = plugins_manager.get_plugin(plugin_id) | |
43 | if plugin: | |
44 | plugin.processReport(report_file) | |
45 | click.echo(plugin.get_json()) | |
46 | else: | |
47 | click.echo(f"Unknown Plugin: {plugin_id}") | |
48 | ||
49 | ||
50 | @cli.command() | |
51 | @click.argument('report_file') | |
52 | def detect(report_file): | |
53 | if not os.path.isfile(report_file): | |
54 | click.echo(f"File {report_file} Don't Exists") | |
55 | else: | |
56 | plugins_manager = PluginsManager() | |
57 | analyzer = ReportAnalyzer(plugins_manager) | |
58 | plugin = analyzer.get_plugin(report_file) | |
59 | if plugin: | |
60 | click.echo(plugin) | |
61 | else: | |
62 | click.echo(f"Failed to detect") | |
63 | ||
0 | from faraday_plugins.commands import cli | |
64 | 1 | |
65 | 2 | if __name__ == "__main__": |
66 | 3 | cli() |
0 | import io | |
1 | import logging | |
2 | import os | |
3 | import sys | |
4 | import json | |
5 | import click | |
6 | import subprocess | |
7 | import shlex | |
8 | import getpass | |
9 | ||
10 | from faraday_plugins.plugins.manager import PluginsManager, ReportAnalyzer, CommandAnalyzer | |
11 | from faraday_plugins.plugins.plugin import PluginByExtension | |
12 | ||
13 | root_logger = logging.getLogger("faraday") | |
14 | if not root_logger.handlers: | |
15 | PLUGIN_DEBUG = os.environ.get("PLUGIN_DEBUG", "0") | |
16 | if PLUGIN_DEBUG == "1": | |
17 | out_hdlr = logging.StreamHandler(sys.stdout) | |
18 | out_hdlr.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s [%(filename)s:%(lineno)s - %(funcName)s()] %(message)s')) | |
19 | out_hdlr.setLevel(logging.DEBUG) | |
20 | root_logger.addHandler(out_hdlr) | |
21 | root_logger.setLevel(logging.DEBUG) | |
22 | ||
23 | ||
24 | @click.group() | |
25 | def cli(): | |
26 | pass | |
27 | ||
28 | ||
29 | @cli.command() | |
30 | @click.option('-cpf', '--custom-plugins-folder', type=str) | |
31 | def list_plugins(custom_plugins_folder): | |
32 | plugins_manager = PluginsManager(custom_plugins_folder) | |
33 | click.echo(click.style("Available Plugins:", fg="cyan")) | |
34 | loaded_plugins = 0 | |
35 | for plugin_id, plugin in plugins_manager.get_plugins(): | |
36 | console_enabled = plugin._command_regex is not None | |
37 | console_enabled_color = "green" if console_enabled else "red" | |
38 | console_enabled_text = click.style(f"{'Yes' if console_enabled else 'No'}", fg=console_enabled_color) | |
39 | report_enabled = isinstance(plugin, PluginByExtension) | |
40 | report_enabled_color = "green" if report_enabled else "red" | |
41 | report_enabled_text = click.style(f"{'Yes' if report_enabled else 'No'}", fg=report_enabled_color) | |
42 | click.echo(f"{plugin.id:15} - [Command: {console_enabled_text:>12} - Report: {report_enabled_text:>12}] - {plugin.name} ") | |
43 | ||
44 | loaded_plugins += 1 | |
45 | click.echo(click.style(f"Loaded Plugins: {loaded_plugins}", fg="cyan")) | |
46 | ||
47 | ||
48 | @cli.command() | |
49 | @click.argument('report_file') | |
50 | @click.option('--plugin_id', type=str) | |
51 | @click.option('-cpf', '--custom-plugins-folder', type=str) | |
52 | @click.option('--summary', is_flag=True) | |
53 | def process_report(report_file, plugin_id, custom_plugins_folder, summary): | |
54 | if not os.path.isfile(report_file): | |
55 | click.echo(click.style(f"File {report_file} Don't Exists", fg="red")) | |
56 | else: | |
57 | plugins_manager = PluginsManager(custom_plugins_folder) | |
58 | analyzer = ReportAnalyzer(plugins_manager) | |
59 | if plugin_id: | |
60 | plugin = plugins_manager.get_plugin(plugin_id) | |
61 | if not plugin: | |
62 | click.echo(click.style(f"Invalid Plugin: {plugin_id}", fg="red")) | |
63 | return | |
64 | else: | |
65 | plugin = analyzer.get_plugin(report_file) | |
66 | if not plugin: | |
67 | click.echo(click.style(f"Failed to detect report: {report_file}", fg="red")) | |
68 | return | |
69 | plugin.processReport(report_file, getpass.getuser()) | |
70 | if summary: | |
71 | click.echo(click.style("\nPlugin Summary: ", fg="cyan")) | |
72 | click.echo(json.dumps(plugin.get_summary(), indent=4)) | |
73 | else: | |
74 | click.echo(click.style("\nFaraday API json: ", fg="cyan")) | |
75 | click.echo(json.dumps(plugin.get_data(), indent=4)) | |
76 | ||
77 | ||
78 | @cli.command() | |
79 | @click.argument('command') | |
80 | @click.option('--plugin_id', type=str) | |
81 | @click.option('-cpf', '--custom-plugins-folder', type=str) | |
82 | @click.option('-dr', '--dont-run', is_flag=True) | |
83 | @click.option('--summary', is_flag=True) | |
84 | def process_command(command, plugin_id, custom_plugins_folder, dont_run, summary): | |
85 | plugins_manager = PluginsManager(custom_plugins_folder) | |
86 | analyzer = CommandAnalyzer(plugins_manager) | |
87 | if plugin_id: | |
88 | plugin = plugins_manager.get_plugin(plugin_id) | |
89 | if not plugin: | |
90 | click.echo(click.style(f"Invalid Plugin: {plugin_id}", fg="red")) | |
91 | return | |
92 | else: | |
93 | plugin = analyzer.get_plugin(command) | |
94 | if not plugin: | |
95 | click.echo(click.style(f"Failed to detect command: {command}", fg="red")) | |
96 | return | |
97 | current_path = os.path.abspath(os.getcwd()) | |
98 | modified_command = plugin.processCommandString(getpass.getuser(), current_path, command) | |
99 | if modified_command: | |
100 | 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") | |
104 | p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
105 | output = io.StringIO() | |
106 | while True: | |
107 | retcode = p.poll() | |
108 | line = p.stdout.readline().decode('utf-8') | |
109 | sys.stdout.write(line) | |
110 | output.write(line) | |
111 | if retcode is not None: | |
112 | extra_lines = map(lambda x: x.decode('utf-8'), p.stdout.readlines()) | |
113 | sys.stdout.writelines(line) | |
114 | output.writelines(extra_lines) | |
115 | break | |
116 | output_value = output.getvalue() | |
117 | if retcode == 0: | |
118 | plugin.processOutput(output_value) | |
119 | if summary: | |
120 | click.echo(click.style("\nPlugin Summary: ", fg="cyan")) | |
121 | click.echo(json.dumps(plugin.get_summary(), indent=4)) | |
122 | else: | |
123 | click.echo(click.style("\nFaraday API json: ", fg="cyan")) | |
124 | click.echo(json.dumps(plugin.get_data(), indent=4)) | |
125 | 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}") | |
130 | ||
131 | ||
132 | ||
133 | @cli.command() | |
134 | @click.argument('report_file') | |
135 | @click.option('-cpf', '--custom-plugins-folder', type=str) | |
136 | def detect_report(report_file, custom_plugins_folder): | |
137 | if not os.path.isfile(report_file): | |
138 | click.echo(click.style(f"File {report_file} Don't Exists", fg="red")) | |
139 | else: | |
140 | plugins_manager = PluginsManager(custom_plugins_folder) | |
141 | analyzer = ReportAnalyzer(plugins_manager) | |
142 | plugin = analyzer.get_plugin(report_file) | |
143 | if plugin: | |
144 | click.echo(click.style(f"Faraday Plugin: {plugin.id}", fg="cyan")) | |
145 | else: | |
146 | click.echo(click.style(f"Failed to detect report: {report_file}", fg="red")) | |
147 | ||
148 | ||
149 | @cli.command() | |
150 | @click.argument('command') | |
151 | @click.option('-cpf', '--custom-plugins-folder', type=str) | |
152 | def detect_command(command, custom_plugins_folder): | |
153 | plugins_manager = PluginsManager(custom_plugins_folder) | |
154 | analyzer = CommandAnalyzer(plugins_manager) | |
155 | plugin = analyzer.get_plugin(command) | |
156 | if plugin: | |
157 | click.echo(click.style(f"Faraday Plugin: {plugin.id}", fg="cyan")) | |
158 | else: | |
159 | click.echo(click.style(f"Failed to detect command: {command}", fg="red")) |
4 | 4 | import sys |
5 | 5 | import json |
6 | 6 | import pkgutil |
7 | import zipfile | |
7 | 8 | from importlib import import_module |
8 | 9 | from importlib.machinery import SourceFileLoader |
10 | import csv | |
11 | from io import StringIO | |
9 | 12 | |
10 | 13 | from . import repo |
11 | 14 | |
65 | 68 | file_extension = file_extension.lower() |
66 | 69 | main_tag = None |
67 | 70 | file_json_keys = {} |
71 | file_csv_headers = set() | |
72 | file_json_keys = set() | |
73 | files_in_zip = set() | |
68 | 74 | logger.debug("Analyze report File") |
69 | 75 | # Try to parse as xml |
70 | 76 | try: |
86 | 92 | logger.debug("Found JSON content on file: %s - Keys: %s", report_path, file_json_keys) |
87 | 93 | except Exception as e: |
88 | 94 | logger.debug("Non JSON content [%s] - %s", report_path, e) |
95 | try: | |
96 | report_file.seek(0) | |
97 | reader_file_string = StringIO(report_file.read().decode('utf-8')) | |
98 | reader = csv.DictReader(reader_file_string) | |
99 | file_csv_headers = set(reader.fieldnames) | |
100 | logger.debug("Found JSON content on file: %s - Keys: %s", report_path, file_json_keys) | |
101 | except Exception as e: | |
102 | logger.debug("Non JSON content [%s] - %s", report_path, e) | |
103 | try: | |
104 | file_zip = zipfile.ZipFile(report_path, "r") | |
105 | files_in_zip = set(file_zip.namelist()) | |
106 | logger.debug("List of files found in ZIP %s", file_zip) | |
107 | except Exception as e: | |
108 | logger.debug("Non ZIP content [%s] - %s", report_path, e) | |
89 | 109 | finally: |
90 | 110 | report_file.close() |
91 | 111 | for _plugin_id, _plugin in self.plugin_manager.get_plugins(): |
92 | 112 | logger.debug("Try plugin: %s", _plugin_id) |
93 | 113 | try: |
94 | 114 | if _plugin.report_belongs_to(main_tag=main_tag, report_path=report_path, |
95 | extension=file_extension, file_json_keys=file_json_keys): | |
115 | extension=file_extension, file_json_keys=file_json_keys, | |
116 | file_csv_headers=file_csv_headers, files_in_zip=files_in_zip): | |
96 | 117 | plugin = _plugin |
97 | 118 | logger.debug("Plugin by File Found: %s", plugin.id) |
98 | 119 | break |
101 | 122 | return plugin |
102 | 123 | |
103 | 124 | |
125 | class CommandAnalyzer: | |
126 | ||
127 | def __init__(self, plugin_manager): | |
128 | self.plugin_manager = plugin_manager | |
129 | ||
130 | def get_plugin(self, command_string): | |
131 | plugin = None | |
132 | logger.debug("Look plugin for command: %s", command_string) | |
133 | for _plugin_id, _plugin in self.plugin_manager.get_plugins(): | |
134 | logger.debug("Try plugin: %s", _plugin_id) | |
135 | try: | |
136 | if _plugin.canParseCommandString(command_string): | |
137 | plugin = _plugin | |
138 | except Exception as e: | |
139 | logger.error("Error in plugin analysis: (%s) %s", _plugin_id, e) | |
140 | return plugin | |
141 | ||
142 | ||
104 | 143 | class PluginsManager: |
105 | 144 | |
106 | def __init__(self): | |
145 | def __init__(self, custom_plugins_folder=None): | |
107 | 146 | self.plugins = {} |
108 | 147 | self.plugin_modules = {} |
109 | self._load_plugins() | |
110 | ||
111 | def _load_plugins(self): | |
148 | self._load_plugins(custom_plugins_folder) | |
149 | ||
150 | def _load_plugins(self, custom_plugins_folder): | |
112 | 151 | logger.info("Loading Native Plugins...") |
113 | 152 | if not self.plugins: |
114 | 153 | for _, name, _ in filter(lambda x: x[2], pkgutil.iter_modules(repo.__path__)): |
117 | 156 | if hasattr(plugin_module, "createPlugin"): |
118 | 157 | plugin_instance = plugin_module.createPlugin() |
119 | 158 | plugin_id = plugin_instance.id.lower() |
159 | if not plugin_instance.auto_load: | |
160 | logger.debug("Skip load plugin [%s]", plugin_id) | |
161 | continue | |
120 | 162 | if plugin_id not in self.plugin_modules: |
121 | 163 | self.plugin_modules[plugin_id] = plugin_module |
122 | 164 | logger.debug("Load Plugin [%s]", name) |
126 | 168 | logger.error("Invalid Plugin [%s]", name) |
127 | 169 | except Exception as e: |
128 | 170 | logger.error("Cant load plugin module: %s [%s]", name, e) |
129 | try: | |
130 | import faraday.server.config | |
131 | if os.path.isdir(faraday.server.config.faraday_server.custom_plugins_folder): | |
171 | if custom_plugins_folder: | |
172 | if os.path.isdir(custom_plugins_folder): | |
132 | 173 | logger.info("Loading Custom Plugins...") |
133 | 174 | dir_name_regexp = re.compile(r"^[\d\w\-\_]+$") |
134 | for name in os.listdir(faraday.server.config.faraday_server.custom_plugins_folder): | |
175 | for name in os.listdir(custom_plugins_folder): | |
135 | 176 | if dir_name_regexp.match(name) and name != "__pycache__": |
177 | module_path = os.path.join(custom_plugins_folder, name) | |
178 | module_filename = os.path.join(module_path, "plugin.py") | |
136 | 179 | try: |
137 | module_path = os.path.join(faraday.server.config.faraday_server.custom_plugins_folder, | |
138 | name) | |
139 | 180 | sys.path.append(module_path) |
140 | module_filename = os.path.join(module_path, "plugin.py") | |
141 | 181 | file_ext = os.path.splitext(module_filename)[1] |
142 | 182 | if file_ext.lower() == '.py': |
143 | 183 | if name not in self.plugin_modules: |
145 | 185 | plugin_module = loader.load_module() |
146 | 186 | plugin_instance = plugin_module.createPlugin() |
147 | 187 | plugin_id = plugin_instance.id.lower() |
188 | if not plugin_instance.auto_load: | |
189 | logger.info("Skip load plugin [%s]", plugin_id) | |
190 | continue | |
148 | 191 | if plugin_id not in self.plugin_modules: |
149 | 192 | self.plugin_modules[plugin_id] = plugin_module |
150 | 193 | else: |
151 | 194 | logger.debug("Plugin with same name already loaded [%s]", name) |
152 | logger.debug('Loading plugin {0}'.format(name)) | |
195 | logger.debug("Load Plugin [%s]", name) | |
153 | 196 | except Exception as e: |
154 | 197 | logger.debug("An error ocurred while loading plugin %s.\n%s", module_filename, |
155 | 198 | traceback.format_exc()) |
156 | 199 | logger.warning(e) |
157 | except Exception as e: | |
158 | logger.info("Can't import faraday server, no custom plugins will be loaded") | |
200 | else: | |
201 | logger.warning("Invalid custom plugins folder [%s]", custom_plugins_folder) | |
159 | 202 | logger.info("%s plugins loaded", len(self.plugin_modules)) |
160 | 203 | |
161 | 204 | def get_plugin(self, plugin_id): |
4 | 4 | |
5 | 5 | """ |
6 | 6 | import os |
7 | import shutil | |
8 | import tempfile | |
9 | ||
10 | from collections import defaultdict | |
11 | ||
12 | import pytz | |
7 | 13 | import re |
8 | 14 | import uuid |
9 | 15 | import logging |
10 | 16 | import simplejson as json |
17 | import zipfile | |
11 | 18 | from datetime import datetime |
19 | import hashlib | |
12 | 20 | |
13 | 21 | |
14 | 22 | logger = logging.getLogger("faraday").getChild(__name__) |
15 | 23 | |
24 | VALID_SERVICE_STATUS = ("open", "closed", "filtered") | |
25 | VULN_SKIP_FIELDS_TO_HASH = ['run_date'] | |
16 | 26 | |
17 | 27 | class PluginBase: |
18 | 28 | # TODO: Add class generic identifier |
23 | 33 | # an existant plugin with the same id. |
24 | 34 | # TODO: Make script that list current ids. |
25 | 35 | self.id = None |
36 | self.auto_load = True | |
26 | 37 | self._rid = id(self) |
27 | 38 | self.version = None |
28 | 39 | self.name = None |
29 | 40 | self.description = "" |
30 | 41 | self._command_regex = None |
31 | 42 | self._output_file_path = None |
43 | self._use_temp_file = False | |
44 | self._delete_temp_file = False | |
45 | self._temp_file_extension = "tmp" | |
46 | self._current_path = None | |
32 | 47 | self.framework_version = None |
33 | 48 | self._completition = {} |
34 | 49 | self._new_elems = [] |
35 | 50 | self._settings = {} |
36 | 51 | self.command_id = None |
37 | self.cache = {} | |
52 | self._cache = {} | |
38 | 53 | self._hosts_cache = {} |
54 | self._service_cache = {} | |
55 | self._vulns_cache = {} | |
39 | 56 | self.start_date = datetime.now() |
40 | 57 | self.logger = logger.getChild(self.__class__.__name__) |
41 | 58 | self.open_options = {"mode": "r", "encoding": "utf-8"} |
50 | 67 | |
51 | 68 | def __str__(self): |
52 | 69 | return f"Plugin: {self.id}" |
70 | ||
71 | def _get_temp_file(self, extension="tmp"): | |
72 | temp_dir = tempfile.gettempdir() | |
73 | temp_filename = f"{self.id}_{next(tempfile._get_candidate_names())}.{extension}" | |
74 | temp_file_path = os.path.join(temp_dir, temp_filename) | |
75 | return temp_file_path | |
76 | ||
77 | @staticmethod | |
78 | def get_utctimestamp(date): | |
79 | if date is not None: | |
80 | try: | |
81 | utc_date = date.astimezone(pytz.UTC) | |
82 | return utc_date.timestamp() | |
83 | except Exception as e: | |
84 | logger.error("Error generating timestamp: %s", e) | |
85 | return None | |
86 | else: | |
87 | return date | |
53 | 88 | |
54 | 89 | @staticmethod |
55 | 90 | def normalize_severity(severity): |
75 | 110 | severity = numeric_severities.get(severity, 'unclassified') |
76 | 111 | return severity |
77 | 112 | |
113 | # Caches | |
78 | 114 | def get_from_cache(self, cache_id): |
79 | return self.cache.get(cache_id, None) | |
80 | ||
81 | def save_host_cache(self, obj): | |
82 | cache_id = f"ip:{obj['ip']}_os:{obj['os']}" | |
115 | return self._cache.get(cache_id, None) | |
116 | ||
117 | def save_host_cache(self, host): | |
118 | cache_id = self.get_host_cache_id(host) | |
83 | 119 | if cache_id not in self._hosts_cache: |
84 | obj_uuid = self.save_cache(obj) | |
85 | self.vulns_data["hosts"].append(obj) | |
120 | obj_uuid = self.save_cache(host) | |
121 | self.vulns_data["hosts"].append(host) | |
86 | 122 | self._hosts_cache[cache_id] = obj_uuid |
87 | 123 | else: |
88 | 124 | obj_uuid = self._hosts_cache[cache_id] |
89 | 125 | return obj_uuid |
90 | 126 | |
127 | def save_service_cache(self, host_id, service): | |
128 | cache_id = self.get_host_service_cache_id(host_id, service) | |
129 | if cache_id not in self._service_cache: | |
130 | obj_uuid = self.save_cache(service) | |
131 | host = self.get_from_cache(host_id) | |
132 | host["services"].append(service) | |
133 | self._service_cache[cache_id] = obj_uuid | |
134 | else: | |
135 | obj_uuid = self._service_cache[cache_id] | |
136 | return obj_uuid | |
137 | ||
138 | def save_service_vuln_cache(self, host_id, service_id, vuln): | |
139 | cache_id = self.get_service_vuln_cache_id(host_id, service_id, vuln) | |
140 | if cache_id not in self._vulns_cache: | |
141 | obj_uuid = self.save_cache(vuln) | |
142 | service = self.get_from_cache(service_id) | |
143 | service["vulnerabilities"].append(vuln) | |
144 | self._vulns_cache[cache_id] = obj_uuid | |
145 | else: | |
146 | obj_uuid = self._vulns_cache[cache_id] | |
147 | return obj_uuid | |
148 | ||
149 | def save_host_vuln_cache(self, host_id, vuln): | |
150 | cache_id = self.get_host_vuln_cache_id(host_id, vuln) | |
151 | if cache_id not in self._vulns_cache: | |
152 | obj_uuid = self.save_cache(vuln) | |
153 | host = self.get_from_cache(host_id) | |
154 | host["vulnerabilities"].append(vuln) | |
155 | self._vulns_cache[cache_id] = obj_uuid | |
156 | else: | |
157 | obj_uuid = self._vulns_cache[cache_id] | |
158 | return obj_uuid | |
159 | ||
160 | @staticmethod | |
161 | def _get_dict_hash(d, keys): | |
162 | return hash(frozenset(map(lambda x: (x, d.get(x, None)), keys))) | |
163 | ||
164 | ||
165 | @classmethod | |
166 | def get_host_cache_id(cls, host): | |
167 | cache_id = cls._get_dict_hash(host, ['ip']) | |
168 | return cache_id | |
169 | ||
170 | @classmethod | |
171 | def get_host_service_cache_id(cls, host_id, service): | |
172 | service_copy = service.copy() | |
173 | service_copy.update({"host_cache_id": host_id}) | |
174 | cache_id = cls._get_dict_hash(service_copy, ['host_cache_id', 'protocol', 'port']) | |
175 | return cache_id | |
176 | ||
177 | @classmethod | |
178 | def get_service_vuln_cache_id(cls, host_id, service_id, vuln): | |
179 | vuln_copy = vuln.copy() | |
180 | vuln_copy.update({"host_cache_id": host_id, "service_cache_id": service_id}) | |
181 | cache_id = cls._get_dict_hash(vuln_copy, ['host_cache_id', 'service_cache_id', 'name', 'desc', 'website', 'path', 'pname', 'method']) | |
182 | return cache_id | |
183 | ||
184 | @classmethod | |
185 | def get_host_vuln_cache_id(cls, host_id, vuln): | |
186 | vuln_copy = vuln.copy() | |
187 | vuln_copy.update({"host_cache_id": host_id}) | |
188 | cache_id = cls._get_dict_hash(vuln_copy, ['host_cache_id', 'name', 'desc', 'website', 'path', 'pname', 'method']) | |
189 | return cache_id | |
190 | ||
91 | 191 | def save_cache(self, obj): |
92 | 192 | obj_uuid = uuid.uuid1() |
93 | self.cache[obj_uuid] = obj | |
193 | self._cache[obj_uuid] = obj | |
94 | 194 | return obj_uuid |
95 | 195 | |
96 | 196 | def report_belongs_to(self, **kwargs): |
142 | 242 | """ |
143 | 243 | return (self._command_regex is not None and |
144 | 244 | self._command_regex.match(current_input.strip()) is not None) |
245 | ||
246 | def processCommandString(self, username, current_path, command_string): | |
247 | """ | |
248 | With this method a plugin can add additional arguments to the | |
249 | command that it's going to be executed. | |
250 | """ | |
251 | self._current_path = current_path | |
252 | if command_string.startswith("sudo"): | |
253 | params = " ".join(command_string.split()[2:]) | |
254 | else: | |
255 | params = " ".join(command_string.split()[1:]) | |
256 | self.vulns_data["command"]["params"] = params | |
257 | self.vulns_data["command"]["user"] = username | |
258 | self.vulns_data["command"]["import_source"] = "shell" | |
259 | if self._use_temp_file: | |
260 | self._delete_temp_file = True | |
261 | self._output_file_path = self._get_temp_file(extension=self._temp_file_extension) | |
262 | return None | |
145 | 263 | |
146 | 264 | def getCompletitionSuggestionsList(self, current_input): |
147 | 265 | """ |
156 | 274 | options[k] = v |
157 | 275 | return options |
158 | 276 | |
159 | def processOutput(self, term_output): | |
160 | output = term_output | |
161 | if self.has_custom_output() and os.path.isfile(self.get_custom_file_path()): | |
277 | def processOutput(self, command_output): | |
278 | if self.has_custom_output(): | |
162 | 279 | self._parse_filename(self.get_custom_file_path()) |
163 | 280 | else: |
164 | self.parseOutputString(output) | |
281 | self.parseOutputString(command_output) | |
165 | 282 | |
166 | 283 | def _parse_filename(self, filename): |
167 | 284 | with open(filename, **self.open_options) as output: |
168 | 285 | self.parseOutputString(output.read()) |
286 | if self._delete_temp_file: | |
287 | try: | |
288 | if os.path.isfile(filename): | |
289 | os.remove(filename) | |
290 | elif os.path.isdir(filename): | |
291 | shutil.rmtree(filename) | |
292 | except Exception as e: | |
293 | self.logger.error("Error on delete file: (%s) [%s]", filename, e) | |
169 | 294 | |
170 | 295 | def processReport(self, filepath, user="faraday"): |
171 | 296 | if os.path.isfile(filepath): |
172 | self._parse_filename(filepath) | |
173 | 297 | self.vulns_data["command"]["params"] = filepath |
174 | 298 | self.vulns_data["command"]["user"] = user |
299 | self.vulns_data["command"]["import_source"] = "report" | |
300 | self._parse_filename(filepath) | |
175 | 301 | else: |
176 | 302 | raise FileNotFoundError(filepath) |
177 | 303 | |
185 | 311 | """ |
186 | 312 | raise NotImplementedError('This method must be implemented.') |
187 | 313 | |
188 | def createAndAddHost(self, name, os="unknown", hostnames=None, mac=None, scan_template="", site_name="", | |
189 | site_importance="", risk_score="", fingerprints="", fingerprints_software=""): | |
314 | def createAndAddHost(self, name, os="unknown", hostnames=None, mac=None, description="", tags=None): | |
190 | 315 | |
191 | 316 | if not hostnames: |
192 | 317 | hostnames = [] |
318 | # Some plugins sends a list with None, we filter empty and None values. | |
319 | hostnames = [hostname for hostname in hostnames if hostname] | |
193 | 320 | if os is None: |
194 | 321 | os = "unknown" |
195 | host = {"ip": name, "os": os, "hostnames": hostnames, "description": "", "mac": mac, | |
196 | "credentials": [], "services": [], "vulnerabilities": [], "scan_template": scan_template, | |
197 | "site_name": site_name, "site_importance": site_importance, "risk_score": risk_score, | |
198 | "fingerprints": fingerprints, "fingerprints_software": fingerprints_software | |
199 | } | |
322 | if tags is None: | |
323 | tags = [] | |
324 | if isinstance(tags, str): | |
325 | tags = [tags] | |
326 | host = {"ip": name, "os": os, "hostnames": hostnames, "description": description, "mac": mac, | |
327 | "credentials": [], "services": [], "vulnerabilities": [], "tags": tags} | |
200 | 328 | host_id = self.save_host_cache(host) |
201 | 329 | return host_id |
202 | 330 | |
235 | 363 | # details="Interface object removed. Use host or service instead. Service will be attached |
236 | 364 | # to Host!") |
237 | 365 | def createAndAddServiceToInterface(self, host_id, interface_id, name, |
238 | protocol="tcp?", ports=None, | |
366 | protocol="tcp", ports=None, | |
239 | 367 | status="open", version="unknown", |
240 | description=""): | |
241 | return self.createAndAddServiceToHost(host_id, name, protocol, ports, status, version, description) | |
368 | description="", tags=None): | |
369 | return self.createAndAddServiceToHost(host_id, name, protocol, ports, status, version, description, tags) | |
242 | 370 | |
243 | 371 | def createAndAddServiceToHost(self, host_id, name, |
244 | protocol="tcp?", ports=None, | |
372 | protocol="tcp", ports=None, | |
245 | 373 | status="open", version="unknown", |
246 | description=""): | |
374 | description="", tags=None): | |
247 | 375 | if ports: |
248 | 376 | if isinstance(ports, list): |
249 | 377 | ports = int(ports[0]) |
250 | 378 | elif isinstance(ports, str): |
251 | 379 | ports = int(ports) |
252 | 380 | |
253 | if status not in ("open", "closed", "filtered"): | |
254 | self.logger.warning('Unknown service status %s. Using "open" instead', status) | |
381 | if status not in VALID_SERVICE_STATUS: | |
255 | 382 | status = 'open' |
383 | if tags is None: | |
384 | tags = [] | |
385 | if isinstance(tags, str): | |
386 | tags = [tags] | |
256 | 387 | service = {"name": name, "protocol": protocol, "port": ports, "status": status, |
257 | "version": version, "description": description, "credentials": [], "vulnerabilities": []} | |
258 | host = self.get_from_cache(host_id) | |
259 | host["services"].append(service) | |
260 | service_id = self.save_cache(service) | |
388 | "version": version, "description": description, "credentials": [], "vulnerabilities": [], | |
389 | "tags": tags} | |
390 | ||
391 | service_id = self.save_service_cache(host_id, service) | |
392 | ||
261 | 393 | return service_id |
262 | 394 | |
263 | 395 | def createAndAddVulnToHost(self, host_id, name, desc="", ref=None, |
264 | severity="", resolution="", vulnerable_since="", scan_id="", pci="", data="", | |
265 | external_id=None): | |
396 | severity="", resolution="", data="", external_id=None, run_date=None, | |
397 | impact=None, custom_fields=None, status="", policyviolations=None, | |
398 | easeofresolution=None, confirmed=False, tags=None): | |
266 | 399 | if ref is None: |
267 | 400 | ref = [] |
268 | vulnerability = {"name": name, " desc": desc, "severity": self.normalize_severity(severity), "refs": ref, | |
269 | "external_id": external_id, "type": "Vulnerability", "resolution": resolution, | |
270 | "vulnerable_since": vulnerable_since, "scan_id": scan_id, "pci": pci, "data": data} | |
271 | host = self.get_from_cache(host_id) | |
272 | ||
273 | host["vulnerabilities"].append(vulnerability) | |
274 | vulnerability_id = len(host["vulnerabilities"]) - 1 | |
401 | if status == "": | |
402 | status = "opened" | |
403 | if impact is None: | |
404 | impact = {} | |
405 | if policyviolations is None: | |
406 | policyviolations = [] | |
407 | if custom_fields is None: | |
408 | custom_fields = {} | |
409 | if tags is None: | |
410 | tags = [] | |
411 | if isinstance(tags, str): | |
412 | tags = [tags] | |
413 | vulnerability = {"name": name, "desc": desc, "severity": self.normalize_severity(severity), "refs": ref, | |
414 | "external_id": external_id, "type": "Vulnerability", "resolution": resolution, "data": data, | |
415 | "custom_fields": custom_fields, "status": status, "impact": impact, "policyviolations": policyviolations, | |
416 | "confirmed": confirmed, "easeofresolution": easeofresolution, "tags": tags | |
417 | } | |
418 | if run_date: | |
419 | vulnerability["run_date"] = self.get_utctimestamp(run_date) | |
420 | vulnerability_id = self.save_host_vuln_cache(host_id, vulnerability) | |
275 | 421 | return vulnerability_id |
276 | 422 | |
277 | 423 | # @deprecation.deprecated(deprecated_in="3.0", removed_in="3.5", |
280 | 426 | # to Host") |
281 | 427 | def createAndAddVulnToInterface(self, host_id, interface_id, name, |
282 | 428 | desc="", ref=None, severity="", |
283 | resolution="", data=""): | |
429 | resolution="", data="", tags=None): | |
284 | 430 | return self.createAndAddVulnToHost(host_id, name, desc=desc, ref=ref, severity=severity, resolution=resolution, |
285 | data=data) | |
431 | data=data, tags=tags) | |
286 | 432 | |
287 | 433 | def createAndAddVulnToService(self, host_id, service_id, name, desc="", |
288 | ref=None, severity="", resolution="", risk="", data="", external_id=None): | |
434 | ref=None, severity="", resolution="", data="", external_id=None, run_date=None, | |
435 | custom_fields=None, policyviolations=None, impact=None, status="", | |
436 | confirmed=False, easeofresolution=None, tags=None): | |
289 | 437 | if ref is None: |
290 | 438 | ref = [] |
439 | if status == "": | |
440 | status = "opened" | |
441 | if impact is None: | |
442 | impact = {} | |
443 | if policyviolations is None: | |
444 | policyviolations = [] | |
445 | if custom_fields is None: | |
446 | custom_fields = {} | |
447 | if tags is None: | |
448 | tags = [] | |
449 | if isinstance(tags, str): | |
450 | tags = [tags] | |
291 | 451 | vulnerability = {"name": name, "desc": desc, "severity": self.normalize_severity(severity), "refs": ref, |
292 | "external_id": external_id, "type": "Vulnerability", "resolution": resolution, "riskB": risk, | |
293 | "data": data} | |
294 | service = self.get_from_cache(service_id) | |
295 | service["vulnerabilities"].append(vulnerability) | |
296 | vulnerability_id = self.save_cache(vulnerability) | |
452 | "external_id": external_id, "type": "Vulnerability", "resolution": resolution, "data": data, | |
453 | "custom_fields": custom_fields, "status": status, "impact": impact, "policyviolations": policyviolations, | |
454 | "easeofresolution": easeofresolution, "confirmed": confirmed, "tags": tags | |
455 | } | |
456 | if run_date: | |
457 | vulnerability["run_date"] = self.get_utctimestamp(run_date) | |
458 | vulnerability_id = self.save_service_vuln_cache(host_id, service_id, vulnerability) | |
297 | 459 | return vulnerability_id |
298 | 460 | |
299 | 461 | def createAndAddVulnWebToService(self, host_id, service_id, name, desc="", |
300 | 462 | ref=None, severity="", resolution="", |
301 | 463 | website="", path="", request="", |
302 | 464 | response="", method="", pname="", |
303 | params="", query="", category="", data="", external_id=None): | |
465 | params="", query="", category="", data="", external_id=None, | |
466 | confirmed=False, status="", easeofresolution=None, impact=None, | |
467 | policyviolations=None, status_code=None, custom_fields=None, run_date=None, tags=None): | |
304 | 468 | if params is None: |
305 | 469 | params = "" |
306 | 470 | if response is None: |
323 | 487 | response = "" |
324 | 488 | if ref is None: |
325 | 489 | ref = [] |
490 | if status == "": | |
491 | status = "opened" | |
492 | if impact is None: | |
493 | impact = {} | |
494 | if policyviolations is None: | |
495 | policyviolations = [] | |
496 | if custom_fields is None: | |
497 | custom_fields = {} | |
498 | if tags is None: | |
499 | tags = [] | |
500 | if isinstance(tags, str): | |
501 | tags = [tags] | |
326 | 502 | vulnerability = {"name": name, "desc": desc, "severity": self.normalize_severity(severity), "refs": ref, |
327 | 503 | "external_id": external_id, "type": "VulnerabilityWeb", "resolution": resolution, |
328 | 504 | "data": data, "website": website, "path": path, "request": request, "response": response, |
329 | "method": method, "pname": pname, "params": params, "query": query, "category": category} | |
330 | service = self.get_from_cache(service_id) | |
331 | service["vulnerabilities"].append(vulnerability) | |
332 | vulnerability_id = self.save_cache(vulnerability) | |
505 | "method": method, "pname": pname, "params": params, "query": query, "category": category, | |
506 | "confirmed": confirmed, "status": status, "easeofresolution": easeofresolution, | |
507 | "impact": impact, "policyviolations": policyviolations, | |
508 | "status_code": status_code, "custom_fields": custom_fields, "tags": tags} | |
509 | if run_date: | |
510 | vulnerability["run_date"] = self.get_utctimestamp(run_date) | |
511 | vulnerability_id = self.save_service_vuln_cache(host_id, service_id, vulnerability) | |
333 | 512 | return vulnerability_id |
334 | ||
335 | 513 | |
336 | 514 | def createAndAddNoteToHost(self, host_id, name, text): |
337 | 515 | return None |
369 | 547 | def get_json(self): |
370 | 548 | self.logger.debug("Generate Json") |
371 | 549 | return json.dumps(self.get_data()) |
550 | ||
551 | def get_summary(self): | |
552 | plugin_json = self.get_data() | |
553 | summary = {'hosts': len(plugin_json['hosts']), 'services': 0, | |
554 | 'hosts_vulns': sum(list(map(lambda x: len(x['vulnerabilities']), plugin_json['hosts']))), | |
555 | 'services_vulns': 0, 'severity_vulns': defaultdict(int), | |
556 | 'vuln_hashes': [] | |
557 | } | |
558 | hosts_with_services = filter(lambda x: len(x['services']) > 0, plugin_json['hosts']) | |
559 | host_services = list(map(lambda x: x['services'], hosts_with_services)) | |
560 | summary['services'] = sum(map(lambda x: len(x), host_services)) | |
561 | services_vulns = 0 | |
562 | for host in plugin_json['hosts']: | |
563 | for vuln in host['vulnerabilities']: | |
564 | summary['severity_vulns'][vuln['severity']] += 1 | |
565 | for services in host_services: | |
566 | for service in services: | |
567 | services_vulns += len(service['vulnerabilities']) | |
568 | for vuln in service['vulnerabilities']: | |
569 | summary['severity_vulns'][vuln['severity']] += 1 | |
570 | summary['services_vulns'] = services_vulns | |
571 | for obj_uuid in self._vulns_cache.values(): | |
572 | vuln = self.get_from_cache(obj_uuid) | |
573 | vuln_copy = vuln.copy() | |
574 | for field in VULN_SKIP_FIELDS_TO_HASH: | |
575 | vuln_copy.pop(field, None) | |
576 | dict_hash = hashlib.sha1(json.dumps(vuln_copy).encode()).hexdigest() | |
577 | summary['vuln_hashes'].append(dict_hash) | |
578 | return summary | |
372 | 579 | |
373 | 580 | # TODO Borrar |
374 | 581 | class PluginTerminalOutput(PluginBase): |
438 | 645 | match = False |
439 | 646 | if super().report_belongs_to(**kwargs): |
440 | 647 | if file_json_keys is None: |
441 | file_json_keys = {} | |
648 | file_json_keys = set() | |
442 | 649 | match = self.json_keys.issubset(file_json_keys) |
443 | 650 | self.logger.debug("Json Keys Match: [%s =/in %s] -> %s", file_json_keys, self.json_keys, match) |
444 | 651 | return match |
445 | # I'm Py3⏎ | |
652 | ||
653 | ||
654 | class PluginCSVFormat(PluginByExtension): | |
655 | ||
656 | def __init__(self): | |
657 | super().__init__() | |
658 | self.extension = ".csv" | |
659 | self.csv_headers = set() | |
660 | ||
661 | def report_belongs_to(self, file_csv_headers=None, **kwargs): | |
662 | match = False | |
663 | if file_csv_headers is None: | |
664 | file_csv_headers = set() | |
665 | if super().report_belongs_to(**kwargs): | |
666 | if isinstance(self.csv_headers, list): | |
667 | match = bool(list(filter(lambda x: x.issubset(file_csv_headers), self.csv_headers))) | |
668 | else: | |
669 | match = self.csv_headers.issubset(file_csv_headers) | |
670 | self.logger.debug("CSV Headers Match: [%s =/in %s] -> %s", file_csv_headers, self.csv_headers, match) | |
671 | return match | |
672 | ||
673 | ||
674 | class PluginZipFormat(PluginByExtension): | |
675 | ||
676 | def __init__(self): | |
677 | super().__init__() | |
678 | self.extension = ".zip" | |
679 | self.files_list = set() | |
680 | ||
681 | def _parse_filename(self, filename): | |
682 | file = zipfile.ZipFile(filename, "r") | |
683 | self.parseOutputString(file) | |
684 | ||
685 | def report_belongs_to(self, files_in_zip=None, **kwargs): | |
686 | match = False | |
687 | if super().report_belongs_to(**kwargs): | |
688 | if files_in_zip is None: | |
689 | files_in_zip = set() | |
690 | match = bool(self.files_list & files_in_zip) | |
691 | self.logger.debug("Files List Match: [%s =/in %s] -> %s", files_in_zip, self.files_list, match) | |
692 | return match | |
693 | ||
694 |
5 | 5 | """ |
6 | 6 | import os |
7 | 7 | import logging |
8 | import faraday_plugins | |
8 | import socket | |
9 | from collections import defaultdict | |
10 | ||
9 | 11 | from urllib.parse import urlsplit |
10 | 12 | |
11 | 13 | |
95 | 97 | yield item |
96 | 98 | |
97 | 99 | |
98 | # I'm Py3 | |
100 | def resolve_hostname(hostname): | |
101 | try: | |
102 | ip_address = socket.gethostbyname(hostname) | |
103 | except Exception as e: | |
104 | return hostname | |
105 | else: | |
106 | return ip_address⏎ |
4 | 4 | |
5 | 5 | """ |
6 | 6 | from urllib.parse import urlsplit |
7 | import socket | |
8 | 7 | import re |
9 | 8 | import os |
9 | ||
10 | from lxml import etree | |
10 | 11 | |
11 | 12 | try: |
12 | 13 | import xml.etree.cElementTree as ET |
17 | 18 | ETREE_VERSION = ET.VERSION |
18 | 19 | |
19 | 20 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
21 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
20 | 22 | |
21 | 23 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
22 | 24 | |
23 | current_path = os.path.abspath(os.getcwd()) | |
24 | 25 | |
25 | 26 | __author__ = "Francisco Amato" |
26 | 27 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
46 | 47 | |
47 | 48 | def __init__(self, xml_output): |
48 | 49 | tree = self.parse_xml(xml_output) |
49 | if tree: | |
50 | if len(tree): | |
50 | 51 | self.sites = list(self.get_items(tree)) |
51 | 52 | else: |
52 | 53 | self.sites = [] |
61 | 62 | @return xml_tree An xml tree instance. None if error. |
62 | 63 | """ |
63 | 64 | try: |
64 | tree = ET.fromstring(xml_output) | |
65 | parser = etree.XMLParser(recover=True) | |
66 | tree = etree.fromstring(xml_output, parser=parser) | |
65 | 67 | except SyntaxError as err: |
66 | 68 | print("SyntaxError: %s. %s", err, xml_output) |
67 | 69 | return None |
117 | 119 | url_data = self.get_url(self.node) |
118 | 120 | |
119 | 121 | self.protocol = url_data.scheme |
120 | self.host = url_data.hostname | |
121 | ||
122 | if url_data.hostname: | |
123 | self.host = url_data.hostname | |
124 | else: | |
125 | self.host = None | |
122 | 126 | # Use the port in the URL if it is defined, or 80 or 443 by default |
123 | 127 | self.port = url_data.port or (443 if url_data.scheme == "https" else 80) |
124 | 128 | |
125 | self.ip = self.resolve(self.host) | |
129 | self.ip = resolve_hostname(self.host) | |
126 | 130 | self.os = self.get_text_from_subnode('Os') |
127 | 131 | self.banner = self.get_text_from_subnode('Banner') |
128 | 132 | self.items = [] |
140 | 144 | return sub_node.text |
141 | 145 | |
142 | 146 | return None |
143 | ||
144 | def resolve(self, host): | |
145 | try: | |
146 | return socket.gethostbyname(host) | |
147 | except: | |
148 | print('[ERROR] Acunetix XML Plugin: Ip of host unknown ' + host) | |
149 | return None | |
150 | return host | |
151 | 147 | |
152 | 148 | def get_url(self, node): |
153 | 149 | url = self.get_text_from_subnode('StartURL') |
176 | 172 | self.response = self.get_text_from_subnode('TechnicalDetails/Response') |
177 | 173 | self.parameter = self.get_text_from_subnode('Parameter') |
178 | 174 | self.uri = self.get_text_from_subnode('Affects') |
179 | self.desc = self.get_text_from_subnode('Description') | |
175 | ||
176 | if self.get_text_from_subnode('Description'): | |
177 | self.desc = self.get_text_from_subnode('Description') | |
178 | else: | |
179 | self.desc = "" | |
180 | 180 | |
181 | 181 | if self.get_text_from_subnode('Recommendation'): |
182 | 182 | self.resolution = self.get_text_from_subnode('Recommendation') |
185 | 185 | |
186 | 186 | if self.get_text_from_subnode('reference'): |
187 | 187 | self.desc += "\nDetails: " + self.get_text_from_subnode('Details') |
188 | else: | |
189 | self.desc += "" | |
190 | 188 | |
191 | 189 | # Add path and params to the description to create different IDs if at |
192 | 190 | # least one of this fields is different |
243 | 241 | for site in parser.sites: |
244 | 242 | if site.ip is None: |
245 | 243 | continue |
246 | host = [] | |
244 | ||
247 | 245 | if site.host != site.ip: |
248 | host = [site.host] | |
246 | host = site.host | |
249 | 247 | h_id = self.createAndAddHost(site.ip, site.os) |
250 | i_id = self.createAndAddInterface( | |
251 | h_id, | |
252 | site.ip, | |
253 | ipv4_address=site.ip, | |
254 | hostname_resolution=host) | |
248 | if site.host is None: | |
249 | i_id = self.createAndAddInterface(h_id, site.ip, ipv4_address=site.ip) | |
250 | else: | |
251 | i_id = self.createAndAddInterface(h_id, site.ip, | |
252 | ipv4_address=site.ip, | |
253 | hostname_resolution=[host]) | |
255 | 254 | s_id = self.createAndAddServiceToInterface( |
256 | 255 | h_id, |
257 | 256 | i_id, |
261 | 260 | version=site.banner, |
262 | 261 | status='open') |
263 | 262 | for item in site.items: |
264 | self.createAndAddVulnWebToService( | |
265 | h_id, | |
266 | s_id, | |
267 | item.name, | |
268 | item.desc, | |
269 | website=site.host, | |
270 | severity=item.severity, | |
271 | resolution=item.resolution, | |
272 | path=item.uri, | |
273 | params=item.parameter, | |
274 | request=item.request, | |
275 | response=item.response, | |
276 | ref=item.ref) | |
263 | ||
264 | if item.desc is None: | |
265 | self.createAndAddVulnWebToService( | |
266 | h_id, | |
267 | s_id, | |
268 | item.name, | |
269 | desc="", | |
270 | website=site.host, | |
271 | severity=item.severity, | |
272 | resolution=item.resolution, | |
273 | path=item.uri, | |
274 | params=item.parameter, | |
275 | request=item.request, | |
276 | response=item.response, | |
277 | ref=item.ref) | |
278 | else: | |
279 | self.createAndAddVulnWebToService( | |
280 | h_id, | |
281 | s_id, | |
282 | item.name, | |
283 | item.desc, | |
284 | website=site.host, | |
285 | severity=item.severity, | |
286 | resolution=item.resolution, | |
287 | path=item.uri, | |
288 | params=item.parameter, | |
289 | request=item.request, | |
290 | response=item.response, | |
291 | ref=item.ref) | |
277 | 292 | del parser |
278 | 293 | |
279 | 294 | def setHost(self): |
281 | 296 | |
282 | 297 | |
283 | 298 | def createPlugin(): |
284 | return AcunetixPlugin() | |
299 | return AcunetixPlugin()⏎ |
2 | 2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | import argparse | |
6 | import random | |
7 | import shlex | |
8 | import tempfile | |
9 | ||
5 | 10 | from faraday_plugins.plugins.plugin import PluginBase |
6 | 11 | import socket |
7 | 12 | import re |
8 | 13 | import os |
9 | 14 | |
10 | current_path = os.path.abspath(os.getcwd()) | |
15 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
16 | ||
11 | 17 | |
12 | 18 | |
13 | 19 | class AmapPlugin(PluginBase): |
21 | 27 | self.version = "5.4" |
22 | 28 | self.options = None |
23 | 29 | self._current_output = None |
24 | self._command_regex = re.compile(r'^(amap|sudo amap).*?') | |
30 | self._command_regex = re.compile(r'^(amap|sudo amap)\s+.*?') | |
31 | self._use_temp_file = True | |
25 | 32 | self._hosts = [] |
26 | 33 | |
27 | def parseOutputString(self, output, debug=False): | |
28 | # if not os.path.exists(self._file_output_path): | |
29 | # return False | |
30 | # | |
31 | # if not debug: | |
32 | # with open(self._file_output_path) as f: | |
33 | # output = f.read() | |
34 | ||
34 | def parseOutputString(self, output): | |
35 | 35 | services = {} |
36 | 36 | for line in output.split('\n'): |
37 | 37 | if line.startswith('#'): |
72 | 72 | self.ip = self.get_ip_6(self.args.m) |
73 | 73 | args['ipv6_address'] = address |
74 | 74 | else: |
75 | self.ip = self.getAddress(self.args.m) | |
75 | self.ip = resolve_hostname(self.args.m) | |
76 | 76 | args['ipv4_address'] = address |
77 | 77 | |
78 | 78 | if address != self.args.m: |
122 | 122 | |
123 | 123 | return ip6[0][4][0] |
124 | 124 | |
125 | def getAddress(self, hostname): | |
126 | """ | |
127 | Returns remote IP address from hostname. | |
128 | """ | |
129 | try: | |
130 | return socket.gethostbyname(hostname) | |
131 | except socket.error as msg: | |
132 | return hostname | |
133 | ||
134 | 125 | def setHost(self): |
135 | 126 | pass |
127 | ||
128 | def processCommandString(self, username, current_path, command_string): | |
129 | """ | |
130 | Adds the -m parameter to get machine readable output. | |
131 | """ | |
132 | super().processCommandString(username, current_path, command_string) | |
133 | arg_match = self.file_arg_re.match(command_string) | |
134 | parser = argparse.ArgumentParser() | |
135 | parser.add_argument('-6', action='store_true') | |
136 | parser.add_argument('-o') | |
137 | parser.add_argument('-m') | |
138 | if arg_match is None: | |
139 | final = re.sub( | |
140 | r"(^.*?amap)", | |
141 | r"\1 -o %s -m " % self._output_file_path, | |
142 | command_string) | |
143 | else: | |
144 | final = re.sub( | |
145 | arg_match.group(1), | |
146 | r"-o %s -m " % self._output_file_path, | |
147 | command_string) | |
148 | ||
149 | cmd = shlex.split(re.sub(r'\-h|\-\-help', r'', final)) | |
150 | if "-6" in cmd: | |
151 | cmd.remove("-6") | |
152 | cmd.insert(1, "-6") | |
153 | ||
154 | args = None | |
155 | if len(cmd) > 4: | |
156 | try: | |
157 | args, unknown = parser.parse_known_args(cmd) | |
158 | except SystemExit: | |
159 | pass | |
160 | ||
161 | self.args = args | |
162 | return final | |
136 | 163 | |
137 | 164 | |
138 | 165 | def createPlugin(): |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | 0 | """ |
4 | 1 | Faraday Penetration Test IDE |
5 | 2 | Copyright (C) 2017 Infobyte LLC (http://www.infobytesec.com/) |
6 | 3 | See the file 'doc/LICENSE' for the license information |
7 | 4 | """ |
8 | 5 | |
9 | import socket | |
10 | 6 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
7 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | 8 | from lxml import objectify |
12 | 9 | from urllib.parse import urlparse |
13 | 10 | |
20 | 17 | __status__ = "Development" |
21 | 18 | |
22 | 19 | |
23 | def cleaner_unicode(string): | |
24 | return string | |
25 | # if string is not None: | |
26 | # return string.encode('ascii', errors='backslashreplace') | |
27 | # else: | |
28 | # return string | |
29 | ||
30 | 20 | |
31 | 21 | class AppscanParser(): |
32 | 22 | |
33 | 23 | def __init__(self, output, logger): |
34 | 24 | self.issue_list = [] |
35 | 25 | self.logger = logger |
36 | self.obj_xml = objectify.fromstring(output.encode('utf-8')) | |
26 | self.obj_xml = objectify.fromstring(output) | |
37 | 27 | |
38 | 28 | def parse_issues(self): |
39 | 29 | issue_type = self.parse_issue_type() |
66 | 56 | hosts_list = [] |
67 | 57 | for host in self.obj_xml['scan-configuration']['scanned-hosts']['item']: |
68 | 58 | hosts_dict = {} |
69 | hosts_dict['ip'] = socket.gethostbyname(host['host'].text) | |
59 | hosts_dict['ip'] = resolve_hostname(host['host'].text) | |
70 | 60 | hosts_dict['hostname'] = host['host'].text |
71 | 61 | hosts_dict['os'] = host['operating-system'].text |
72 | 62 | hosts_dict['port'] = host['port'].text |
143 | 133 | self.options = None |
144 | 134 | self.open_options = {"mode": "r", "encoding": "utf-8"} |
145 | 135 | |
146 | def parseOutputString(self, output, debug=False): | |
136 | def parseOutputString(self, output): | |
147 | 137 | try: |
148 | 138 | parser = AppscanParser(output, self.logger) |
149 | 139 | issues = parser.parse_issues() |
157 | 147 | ports=[host['port']], |
158 | 148 | protocol="tcp?HTTP") |
159 | 149 | if host['port']: |
160 | key_url = f"{host['scheme']}://{host['hostname']}:{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']}" | |
161 | 154 | else: |
162 | 155 | key_url = f"{host['scheme']}://{host['hostname']}" |
163 | 156 | hosts_dict[key_url] = {'host_id': host_id, 'service_id': service_id} |
164 | 157 | for issue in issues: |
165 | url_parsed = urlparse(str(issue['url'])) | |
166 | url_string = '://'.join([url_parsed.scheme, url_parsed.netloc]) | |
158 | url_parsed = urlparse(issue['url']) | |
159 | url_string = f'{url_parsed.scheme}://{url_parsed.netloc}' | |
167 | 160 | for key in hosts_dict: |
168 | 161 | if url_string == key: |
169 | 162 | h_id = hosts_dict[key]['host_id'] |
180 | 173 | self.createAndAddVulnWebToService( |
181 | 174 | h_id, |
182 | 175 | s_id, |
183 | cleaner_unicode(issue["name"]), | |
184 | desc=cleaner_unicode(issue["issue_description"]) if "issue_description" in issue else "", | |
176 | issue["name"], | |
177 | desc=issue["issue_description"] if "issue_description" in issue else "", | |
185 | 178 | ref=refs, |
186 | 179 | severity=issue["severity"], |
187 | resolution=cleaner_unicode(issue["recomendation"]), | |
180 | resolution=issue["recomendation"], | |
188 | 181 | website=url_parsed.netloc, |
189 | 182 | path=url_parsed.path, |
190 | request=cleaner_unicode(issue["request"]) if "request" in issue else "", | |
191 | response=cleaner_unicode(issue["response"]) if issue["response"] else "", | |
183 | request=issue["request"] if "request" in issue else "", | |
184 | response=issue["response"] if issue["response"] else "", | |
192 | 185 | method=issue["method"] if issue["method"] else "") |
193 | 186 | except Exception as e: |
194 | 187 | self.logger.error("Parsing Output Error: %s", e) |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | 0 | """ |
4 | 1 | Faraday Penetration Test IDE |
5 | 2 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) |
6 | 3 | See the file 'doc/LICENSE' for the license information |
7 | 4 | """ |
8 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
9 | import socket | |
10 | import random | |
11 | 5 | import re |
12 | 6 | from urllib.parse import urlparse |
13 | 7 | import os |
8 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
9 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
14 | 10 | |
15 | 11 | try: |
16 | 12 | import xml.etree.cElementTree as ET |
42 | 38 | try: |
43 | 39 | tree = ET.fromstring(xml_output) |
44 | 40 | except SyntaxError as err: |
45 | print('SyntaxError In xml: %s. %s' % (err, xml_output)) | |
46 | 41 | return None |
47 | 42 | return tree |
48 | 43 | |
86 | 81 | description = self.node.find(tag) |
87 | 82 | |
88 | 83 | if description is not None and description.text is not None: |
89 | return description.text.encode('ascii', 'ignore') | |
84 | return description.text | |
90 | 85 | else: |
91 | 86 | return 'None' |
92 | 87 | |
101 | 96 | result = main_entity.find(child_tag) |
102 | 97 | |
103 | 98 | if result is not None and result.text is not None: |
104 | return result.text.encode('ascii', 'ignore') | |
99 | return result.text | |
105 | 100 | else: |
106 | 101 | return 'None' |
107 | 102 | |
144 | 139 | try: |
145 | 140 | |
146 | 141 | raw_data = self.node.find('page').find('request').find('raw') |
147 | data = raw_data.text.encode('ascii', 'ignore') | |
142 | data = raw_data.text | |
148 | 143 | return data |
149 | 144 | |
150 | 145 | except: |
155 | 150 | # Get data about response. |
156 | 151 | try: |
157 | 152 | |
158 | raw_data = self.node.find('page').find( | |
159 | 'response').find('raw_headers') | |
160 | data = raw_data.text.encode('ascii', 'ignore') | |
153 | raw_data = self.node.find('page').find('response').find('raw_headers') | |
154 | data = raw_data.text | |
161 | 155 | return data |
162 | 156 | |
163 | 157 | except: |
240 | 234 | self.plugins_node = plugins_node |
241 | 235 | self.healthmap = self.getHealthmap() |
242 | 236 | self.waf = self.getWaf() |
243 | self.ip = plugins_node.find('resolver').find('results').find('hostname').get('ipaddress') | |
237 | try: | |
238 | self.ip = plugins_node.find('resolver').find('results') \ | |
239 | .find('hostname').get('ipaddress') | |
240 | except Exception: | |
241 | self.ip = '0.0.0.0' | |
244 | 242 | |
245 | 243 | def getHealthmap(self): |
246 | 244 | |
327 | 325 | self.version = '1.3.2' |
328 | 326 | self.framework_version = '1.0.0' |
329 | 327 | self.options = None |
330 | self._command_regex = re.compile(r'^(arachni |\.\/arachni).*?') | |
328 | self._command_regex = re.compile(r'^(arachni|\.\/arachni)\s+.*?') | |
331 | 329 | self.protocol = None |
332 | 330 | self.hostname = None |
333 | 331 | self.port = '80' |
334 | 332 | self.address = None |
333 | self._use_temp_file = True | |
334 | self._temp_file_extension = ["afr", "xml"] | |
335 | 335 | |
336 | 336 | def report_belongs_to(self, **kwargs): |
337 | 337 | if super().report_belongs_to(**kwargs): |
341 | 341 | return re.search("/Arachni/arachni/", output) is not None |
342 | 342 | return False |
343 | 343 | |
344 | def _parse_filename(self, filename): | |
345 | """ | |
346 | This plugin gets a dict of files, not just one file if it runs the command. | |
347 | We just need the xml. | |
348 | """ | |
349 | if isinstance(filename, dict): | |
350 | filename = filename['xml'] | |
351 | with open(filename, **self.open_options) as output: | |
352 | self.parseOutputString(output.read()) | |
353 | if self._delete_temp_file: | |
354 | if isinstance(filename, dict): | |
355 | for _file in filename.values(): | |
356 | try: | |
357 | os.remove(_file) | |
358 | except Exception as e: | |
359 | self.logger.error("Error on delete file: (%s) [%s]", _file, e) | |
360 | else: | |
361 | try: | |
362 | os.remove(filename) | |
363 | except Exception as e: | |
364 | self.logger.error("Error on delete file: (%s) [%s]", filename, e) | |
365 | ||
344 | 366 | def parseOutputString(self, output, debug=False): |
345 | 367 | """ |
346 | 368 | This method will discard the output the shell sends, it will read it |
347 | 369 | from the xml where it expects it to be present. |
348 | 370 | """ |
349 | ||
350 | 371 | parser = ArachniXmlParser(output) |
351 | 372 | |
352 | 373 | # Check xml parsed ok... |
353 | 374 | if not parser.system: |
354 | print('Error in xml report... Exiting...') | |
355 | 375 | return |
356 | 376 | |
357 | 377 | self.hostname = self.getHostname(parser.system.url) |
358 | self.address = self.getAddress(parser.plugins.ip) | |
359 | ||
378 | self.address = resolve_hostname(parser.plugins.ip) | |
360 | 379 | |
361 | 380 | # Create host and interface |
362 | 381 | host_id = self.createAndAddHost(self.address) |
412 | 431 | """ |
413 | 432 | Use bash to run sequentialy arachni and arachni_reporter |
414 | 433 | """ |
415 | ||
416 | afr_output_file_path = os.path.join( | |
417 | self.data_path, | |
418 | "%s_%s_output-%s.afr" % ( | |
419 | self.get_ws(), | |
420 | self.id, | |
421 | random.uniform(1, 10)) | |
422 | ) | |
423 | ||
434 | # Dont call the parent beacuse this plugin needs a different implementation | |
435 | if command_string.startswith("sudo"): | |
436 | params = " ".join(command_string.split()[2:]) | |
437 | else: | |
438 | params = " ".join(command_string.split()[1:]) | |
439 | self.vulns_data["command"]["params"] = params | |
440 | self.vulns_data["command"]["user"] = username | |
441 | self._output_file_path = {} | |
442 | self._delete_temp_file = True | |
443 | for ext in self._temp_file_extension: | |
444 | self._output_file_path[ext] = self._get_temp_file(extension=ext) | |
445 | afr_file_path = self._output_file_path['afr'] | |
446 | xml_file_path = self._output_file_path['xml'] | |
424 | 447 | report_arg_re = r"^.*(--report-save-path[=\s][^\s]+).*$" |
425 | arg_match = re.match(report_arg_re,command_string) | |
448 | arg_match = re.match(report_arg_re, command_string) | |
426 | 449 | if arg_match is None: |
427 | main_cmd = re.sub(r"(^.*?arachni)", | |
428 | r"\1 --report-save-path=%s" % afr_output_file_path, | |
429 | command_string) | |
430 | else: | |
431 | main_cmd = re.sub(arg_match.group(1), | |
432 | r"--report-save-path=%s" % afr_output_file_path, | |
433 | command_string) | |
450 | main_cmd = re.sub(r"(^.*?arachni)", r"\1 --report-save-path=%s" % afr_file_path, command_string) | |
451 | else: | |
452 | main_cmd = re.sub(arg_match.group(1), r"--report-save-path=%s" % afr_file_path, command_string) | |
434 | 453 | |
435 | 454 | # add reporter |
436 | self._output_file_path = re.sub('.afr', '.xml', afr_output_file_path) | |
437 | 455 | cmd_prefix_match = re.match(r"(^.*?)arachni ", command_string) |
438 | 456 | cmd_prefix = cmd_prefix_match.group(1) |
439 | reporter_cmd = "%s%s --reporter=\"xml:outfile=%s\" \"%s\"" % ( | |
440 | cmd_prefix, | |
441 | "arachni_reporter", | |
442 | self._output_file_path, | |
443 | afr_output_file_path) | |
457 | reporter_cmd = "%s%s --reporter=\"xml:outfile=%s\" \"%s\"" % (cmd_prefix, "arachni_reporter", xml_file_path, | |
458 | afr_file_path) | |
444 | 459 | return "/usr/bin/env -- bash -c '%s 2>&1 && if [ -e \"%s\" ];then %s 2>&1;fi'" % (main_cmd, |
445 | afr_output_file_path, | |
460 | afr_file_path, | |
446 | 461 | reporter_cmd) |
447 | 462 | |
448 | 463 | def getHostname(self, url): |
460 | 475 | |
461 | 476 | return self.hostname |
462 | 477 | |
463 | def getAddress(self, hostname): | |
464 | ||
465 | # Returns remote IP address from hostname. | |
466 | try: | |
467 | return socket.gethostbyname(hostname) | |
468 | except socket.error as msg: | |
469 | return self.hostname | |
470 | ||
471 | 478 | |
472 | 479 | def createPlugin(): |
473 | 480 | return ArachniPlugin() |
30 | 30 | self.framework_version = "1.0.0" |
31 | 31 | self.options = None |
32 | 32 | self._current_output = None |
33 | self._command_regex = re.compile( | |
34 | r'^(sudo arp-scan|\.\/arp-scan|arp-scan).*?') | |
33 | self._command_regex = re.compile(r'^(sudo arp-scan|\.\/arp-scan|arp-scan)\s+.*?') | |
35 | 34 | self._host_ip = None |
36 | 35 | |
37 | def parseOutputString(self, output, debug=False): | |
36 | def parseOutputString(self, output): | |
38 | 37 | |
39 | 38 | host_info = re.search( |
40 | 39 | 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)", |
59 | 58 | |
60 | 59 | return True |
61 | 60 | |
62 | def processCommandString(self, username, current_path, command_string): | |
63 | return | |
64 | 61 | |
65 | 62 | |
66 | 63 | def createPlugin(): |
67 | 64 | return CmdArpScanPlugin() |
68 | 65 | |
69 | # I'm Py3⏎ |
32 | 32 | self.options = None |
33 | 33 | self._current_output = None |
34 | 34 | self.target = None |
35 | self._command_regex = re.compile(r'^(beef|sudo beef|\.\/beef).*?') | |
35 | self._command_regex = re.compile(r'^(beef|sudo beef|\.\/beef)\s+.*?') | |
36 | 36 | |
37 | 37 | self.addSetting("Host", str, "http://127.0.0.1:3000/") |
38 | 38 | self.addSetting( |
93 | 93 | ref=["http://http://beefproject.com/"], |
94 | 94 | severity=3) |
95 | 95 | |
96 | def processCommandString(self, username, current_path, command_string): | |
97 | return None | |
98 | 96 | |
99 | 97 | def setHost(self): |
100 | 98 | pass |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | 5 | import re |
6 | import socket | |
7 | 6 | from urllib.parse import urlparse |
8 | from faraday_plugins.plugins.plugin import PluginBase | |
9 | 7 | |
10 | 8 | __author__ = "Roberto Focke" |
11 | 9 | __copyright__ = "Copyright (c) 2017, Infobyte LLC" |
12 | 10 | __license__ = "" |
13 | 11 | __version__ = "1.0.0" |
12 | ||
13 | from faraday_plugins.plugins.plugin import PluginBase | |
14 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
14 | 15 | |
15 | 16 | |
16 | 17 | class brutexss (PluginBase): |
23 | 24 | self.version = "1.0.0" |
24 | 25 | self.protocol ='tcp' |
25 | 26 | self._command_regex = re.compile(r'^(sudo brutexss|brutexss|sudo brutexss\.py|brutexss\.py|python brutexss\.py|' |
26 | r'\.\/brutexss\.py).*?') | |
27 | r'\.\/brutexss\.py)\s+.*?') | |
27 | 28 | |
28 | 29 | def parseOutputString(self, output, debug=False): |
29 | 30 | lineas = output.split("\n") |
31 | 32 | found_vuln = False |
32 | 33 | for linea in lineas: |
33 | 34 | if linea.find("is available! Good!") > 0: |
34 | print(linea) | |
35 | 35 | url = re.findall('(?:[-\w.]|(?:%[\da-fA-F]{2}))+', linea)[0] |
36 | 36 | port = 80 |
37 | 37 | if urlparse(url).scheme == 'https': |
45 | 45 | parametro.append(vuln_list[1]) |
46 | 46 | found_vuln=len(parametro) > 0 |
47 | 47 | host_id = self.createAndAddHost(url) |
48 | address=socket.gethostbyname(url) | |
48 | address = resolve_hostname(url) | |
49 | 49 | interface_id = self.createAndAddInterface(host_id, address, ipv4_address=address, |
50 | 50 | hostname_resolution=[url]) |
51 | 51 | service_id = self.createAndAddServiceToInterface(host_id, interface_id, self.protocol, 'tcp', |
52 | 52 | ports=[port], status='Open', version="", |
53 | 53 | description="") |
54 | 54 | if found_vuln: |
55 | self.createAndAddVulnWebToService(host_id,service_id, name="xss", desc="XSS", ref='', severity='med', | |
55 | self.createAndAddVulnWebToService(host_id, service_id, name="xss", desc="XSS", ref='', severity='med', | |
56 | 56 | website=url, path='', method='', pname='', params=''.join(parametro), |
57 | 57 | request='', response='') |
58 | 58 | |
59 | def processCommandString(self, username, current_path, command_string): | |
60 | return None | |
61 | 59 | |
62 | 60 | |
63 | 61 | def createPlugin(): |
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/) |
8 | 5 | """ |
9 | 6 | import re |
10 | 7 | import os |
11 | import sys | |
12 | 8 | import base64 |
13 | 9 | from bs4 import BeautifulSoup, Comment |
14 | 10 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
26 | 22 | |
27 | 23 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
28 | 24 | |
29 | current_path = os.path.abspath(os.getcwd()) | |
30 | 25 | |
31 | 26 | __author__ = "Francisco Amato" |
32 | 27 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
269 | 264 | |
270 | 265 | del parser |
271 | 266 | |
272 | def processCommandString(self, username, current_path, command_string): | |
273 | return None | |
274 | 267 | |
275 | 268 | def removeHtml(self, markup): |
276 | 269 | soup = BeautifulSoup(markup, "html.parser") |
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 | # I'm Py3⏎ |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | """ | |
5 | import re | |
6 | from urllib.parse import urlparse | |
7 | ||
8 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
9 | ||
10 | ||
11 | try: | |
12 | import xml.etree.cElementTree as ET | |
13 | except ImportError: | |
14 | import xml.etree.ElementTree as ET | |
15 | ||
16 | __author__ = 'Blas Moyano' | |
17 | __copyright__ = 'Copyright 2020, Faraday Project' | |
18 | __credits__ = ['Blas Moyano'] | |
19 | __license__ = '' | |
20 | __version__ = '1.0.0' | |
21 | __status__ = 'Development' | |
22 | ||
23 | ||
24 | class CheckmarxXmlParser: | |
25 | def __init__(self, xml_output): | |
26 | self.tree = self.parse_xml(xml_output) | |
27 | if self.tree: | |
28 | self.cx_xml_results_attribs = self.tree.attrib | |
29 | self.query = self.getQuery(self.tree) | |
30 | else: | |
31 | self.query = None | |
32 | ||
33 | def parse_xml(self, xml_output): | |
34 | try: | |
35 | tree = ET.fromstring(xml_output) | |
36 | except SyntaxError as err: | |
37 | print('SyntaxError In xml: %s. %s' % (err, xml_output)) | |
38 | return None | |
39 | return tree | |
40 | ||
41 | def getQuery(self, tree): | |
42 | for self.query_node in tree: | |
43 | yield Querys(self.query_node) | |
44 | ||
45 | ||
46 | class Querys(): | |
47 | def __init__(self, query_node): | |
48 | ||
49 | self.query_attrib = query_node.attrib | |
50 | self.result, self.path, self.path_node = self.get_Result(query_node.findall('Result')) | |
51 | ||
52 | def get_Result(self, result): | |
53 | result_atr = [] | |
54 | path = [] | |
55 | path_node = [] | |
56 | for r in result: | |
57 | self.result_attrib = r.attrib | |
58 | self.path_attrib = r.find('Path').attrib | |
59 | self.path_node = self.get_path_node_info(r.find('Path').findall('PathNode')) | |
60 | result_atr.append(self.result_attrib) | |
61 | path.append(self.path_attrib) | |
62 | path_node.append(self.path_node) | |
63 | return result_atr, path, path_node | |
64 | ||
65 | def get_path_node_info(self, path_node): | |
66 | lista = [] | |
67 | for pn in path_node: | |
68 | lista_v = [] | |
69 | for info_pn in pn: | |
70 | if info_pn.tag == 'Snippet': | |
71 | valor = ( | |
72 | 'Number', info_pn.find('Line').find('Number').text, 'Code', info_pn.find('Line').find('Code').text) | |
73 | else: | |
74 | valor = (info_pn.tag, info_pn.text) | |
75 | lista_v.append(valor) | |
76 | lista.append(lista_v) | |
77 | return lista | |
78 | ||
79 | ||
80 | class CheckmarxPlugin(PluginXMLFormat): | |
81 | def __init__(self): | |
82 | super().__init__() | |
83 | self.identifier_tag = ["CxXMLResults"] | |
84 | self.id = 'Checkmarx' | |
85 | self.name = 'Checkmarx XML Output Plugin' | |
86 | self.plugin_version = '1.0.0' | |
87 | self.version = '1.0.0' | |
88 | self.framework_version = '1.0.0' | |
89 | self.options = None | |
90 | ||
91 | ||
92 | def parseOutputString(self, output): | |
93 | parser = CheckmarxXmlParser(output) | |
94 | if not parser.query: | |
95 | self.logger.warning('Error in xml report... Exiting...') | |
96 | return | |
97 | ||
98 | url = urlparse(parser.cx_xml_results_attribs['DeepLink']) | |
99 | port = url.port | |
100 | if not port: | |
101 | if url.scheme == 'https': | |
102 | port = 443 | |
103 | elif url.scheme == 'http': | |
104 | port = 80 | |
105 | else: | |
106 | port = 0 | |
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) | |
120 | for vulns in parser.query: | |
121 | refs = [] | |
122 | categories = 'categories' in vulns.query_attrib | |
123 | vuln_desc = '' | |
124 | if categories: | |
125 | vuln_desc = vulns.query_attrib['categories'] | |
126 | vuln_name = vulns.query_attrib['name'] | |
127 | vuln_severity = vulns.query_attrib['Severity'] | |
128 | vuln_external_id = vulns.query_attrib['id'] | |
129 | refs.append(f'CWE-{vulns.query_attrib["cweId"]}') | |
130 | data = '' | |
131 | for files_data in vulns.path_node: | |
132 | for file_data in files_data: | |
133 | data += 60 * '-' + '\n' | |
134 | for row_data in file_data: | |
135 | data += ' '.join([data for data in row_data if data]) + '\n' | |
136 | ||
137 | for v_result in vulns.result: | |
138 | refs.append(v_result['DeepLink']) | |
139 | refs.append(v_result['FileName']) | |
140 | ||
141 | self.createAndAddVulnToHost(host_id, vuln_name, severity=vuln_severity, | |
142 | resolution=data, external_id=vuln_external_id) | |
143 | ||
144 | self.createAndAddVulnWebToService(host_id, service_to_interface, vuln_name, | |
145 | desc=vuln_desc, severity=vuln_severity, | |
146 | resolution=data, ref=refs) | |
147 | ||
148 | ||
149 | def createPlugin(): | |
150 | return CheckmarxPlugin() | |
151 |
0 | """ | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | """ | |
6 | # I'm Py3⏎ |
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 | from faraday_plugins.plugins.plugin import PluginCSVFormat | |
8 | from urllib.parse import urlparse | |
9 | import csv | |
10 | import io | |
11 | import dateutil | |
12 | ||
13 | ||
14 | __author__ = "Blas" | |
15 | __copyright__ = "Copyright (c) 2019, Infobyte LLC" | |
16 | __credits__ = ["Blas"] | |
17 | __license__ = "" | |
18 | __version__ = "1.0.0" | |
19 | __maintainer__ = "Blas" | |
20 | __email__ = "[email protected]" | |
21 | __status__ = "Development" | |
22 | ||
23 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
24 | ||
25 | ||
26 | class CobaltParser: | |
27 | """ | |
28 | The objective of this class is to parse an CSV file generated by the Cobalt tool. | |
29 | ||
30 | TODO: Handle errors. | |
31 | TODO: Test Cobalt output version. Handle what happens if the parser doesn't support it. | |
32 | TODO: Test cases. | |
33 | ||
34 | @param Cobalt_filepath A proper simple report generated by Cobalt | |
35 | """ | |
36 | def __init__(self, output): | |
37 | ||
38 | reader = csv.DictReader(io.StringIO(output)) | |
39 | self.headers = reader.fieldnames | |
40 | self.rows = [] | |
41 | for row in reader: | |
42 | for k, v in row.items(): | |
43 | if v.startswith("'"): | |
44 | row[k] = v[1:] | |
45 | self.rows.append(row) | |
46 | ||
47 | ||
48 | class CobaltPlugin(PluginCSVFormat): | |
49 | """ | |
50 | Example plugin to parse Cobalt output. | |
51 | """ | |
52 | ||
53 | def __init__(self): | |
54 | super().__init__() | |
55 | self.csv_headers = [{'Token'}, {'Tag'}] | |
56 | self.id = "Cobalt" | |
57 | self.name = "Cobalt CSV Output Plugin" | |
58 | self.plugin_version = "0.0.1" | |
59 | self.version = "0.0.1" | |
60 | self.framework_version = "1.0.1" | |
61 | ||
62 | def parseOutputString(self, output): | |
63 | try: | |
64 | parser = CobaltParser(output) | |
65 | except: | |
66 | print("Error parser output") | |
67 | return None | |
68 | ||
69 | for row in parser.rows: | |
70 | url = row['BrowserUrl'] | |
71 | if not url: | |
72 | continue | |
73 | url_data = urlparse(url) | |
74 | scheme = url_data.scheme | |
75 | port = url_data.port | |
76 | try: | |
77 | run_date = dateutil.parser.parse(row['CreatedAt']) | |
78 | except: | |
79 | run_date = None | |
80 | if url_data.port is None: | |
81 | if scheme == 'https': | |
82 | port = 443 | |
83 | elif scheme == 'http': | |
84 | port = 80 | |
85 | else: | |
86 | port = url_data.port | |
87 | name = resolve_hostname(url_data.netloc) | |
88 | references = [] | |
89 | if row['RefKey']: | |
90 | references.append(row['RefKey']) | |
91 | if row['ResearcherUrl']: | |
92 | references.append(row['ResearcherUrl']) | |
93 | references.append(row['ReportUrl']) | |
94 | request = row['HttpRequest'] if row['HttpRequest'] else row['BrowserUrl'] | |
95 | h_id = self.createAndAddHost(name=name, hostnames=[url_data.netloc]) | |
96 | s_id = self.createAndAddServiceToHost(h_id, scheme, "tcp", ports=port, status="open") | |
97 | self.createAndAddVulnWebToService(h_id, s_id, name=row['Title'], desc=row['Description'], | |
98 | ref=references, resolution=row['SuggestedFix'], | |
99 | website=url_data.netloc, request=request, | |
100 | pname=url_data.params, category=row['Type'], path=url_data.path, | |
101 | data=row['StepsToReproduce'], external_id=row['Tag'], run_date=run_date) | |
102 | ||
103 | ||
104 | def createPlugin(): | |
105 | return CobaltPlugin() | |
106 |
5 | 5 | See the file 'doc/LICENSE' for the license information |
6 | 6 | """ |
7 | 7 | import re |
8 | import socket | |
9 | ||
10 | from faraday_plugins.plugins.plugin import PluginBase | |
11 | 8 | |
12 | 9 | __author__ = u"Andres Tarantini" |
13 | 10 | __copyright__ = u"Copyright (c) 2015 Andres Tarantini" |
17 | 14 | __maintainer__ = u"Andres Tarantini" |
18 | 15 | __email__ = u"[email protected]" |
19 | 16 | __status__ = u"Development" |
17 | ||
18 | from faraday_plugins.plugins.plugin import PluginBase | |
19 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
20 | 20 | |
21 | 21 | |
22 | 22 | class DigPlugin(PluginBase): |
30 | 30 | self.name = u"DiG" |
31 | 31 | self.plugin_version = u"0.0.1" |
32 | 32 | self.version = u"9.9.5-3" |
33 | self._command_regex = re.compile(r'^(dig).*?') | |
33 | self._command_regex = re.compile(r'^(dig)\s+.*?') | |
34 | 34 | |
35 | 35 | def parseOutputString(self, output): |
36 | 36 | # Ignore all lines that start with ";" |
63 | 63 | if result.get(u"type") == u"A": # A = IPv4 address from dig |
64 | 64 | ip_address = result.get(u"data")[0] |
65 | 65 | else: # if not, from socket |
66 | ip_address = socket.gethostbyname(domain) | |
66 | ip_address = resolve_hostname(domain) | |
67 | 67 | |
68 | 68 | # Create host |
69 | 69 | host_id = self.createAndAddHost(ip_address) |
2 | 2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | 5 | import re |
7 | import socket | |
8 | 6 | |
9 | 7 | __author__ = "Federico Fernandez - @q3rv0" |
10 | 8 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
13 | 11 | __maintainer__ = "Federico Fernandez" |
14 | 12 | __email__ = "[email protected]" |
15 | 13 | __status__ = "Development" |
14 | ||
15 | from faraday_plugins.plugins.plugin import PluginBase | |
16 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
16 | 17 | |
17 | 18 | |
18 | 19 | class dirbPlugin(PluginBase): |
39 | 40 | |
40 | 41 | def getIP(self, host): |
41 | 42 | try: |
42 | ip = socket.gethostbyname(host) | |
43 | ip = resolve_hostname(host) | |
43 | 44 | except Exception: |
44 | 45 | pass |
45 | 46 | |
72 | 73 | |
73 | 74 | self.text = '\n'.join(self.text) |
74 | 75 | |
75 | def parseOutputString(self, output, debug=False): | |
76 | def parseOutputString(self, output): | |
76 | 77 | |
77 | 78 | url = re.search(r"URL_BASE: " + self.regexpUrl, output) |
78 | 79 | paths = self.pathsDirListing(output) |
106 | 107 | Adds the -oX parameter to get xml output to the command string that the |
107 | 108 | user has set. |
108 | 109 | """ |
109 | ||
110 | super().processCommandString(username, current_path, command_string) | |
110 | 111 | no_stop_on_warn_msg_re = r"\s+-w" |
111 | 112 | arg_search = re.search(no_stop_on_warn_msg_re,command_string) |
112 | 113 | extra_arg = "" |
5 | 5 | import re |
6 | 6 | import json |
7 | 7 | import shlex |
8 | import socket | |
9 | 8 | import argparse |
10 | import tempfile | |
11 | 9 | import urllib.parse as urlparse |
12 | from faraday_plugins.plugins.plugin import PluginTerminalOutput | |
13 | from faraday_plugins.plugins.plugins_utils import get_vulnweb_url_fields | |
14 | import os | |
10 | from faraday_plugins.plugins.plugin import PluginBase | |
11 | from faraday_plugins.plugins.plugins_utils import get_vulnweb_url_fields, resolve_hostname | |
15 | 12 | |
16 | 13 | |
17 | 14 | __author__ = "Matías Lang" |
53 | 50 | } |
54 | 51 | |
55 | 52 | |
56 | class DirsearchPlugin(PluginTerminalOutput): | |
53 | class DirsearchPlugin(PluginBase): | |
57 | 54 | def __init__(self): |
58 | 55 | super().__init__() |
59 | 56 | self.id = "dirsearch" |
60 | 57 | self.name = "dirsearch" |
61 | 58 | self.plugin_version = "0.0.1" |
62 | 59 | self.version = "0.0.1" |
63 | self._command_regex = re.compile( | |
64 | r'^(sudo )?(python[0-9\.]? )?dirsearch(\.py)?') | |
65 | self.ignore_parsing = False | |
66 | self.json_report_file = None | |
60 | self._command_regex = re.compile(r'^(sudo )?(python[0-9\.]? )?dirsearch(\.py)\s+?') | |
67 | 61 | self.addSetting("Ignore 403", str, "1") |
62 | self._use_temp_file = True | |
63 | self._temp_file_extension = "json" | |
68 | 64 | |
69 | def parseOutputString(self, output, debug=False): | |
70 | if self.ignore_parsing: | |
71 | return | |
72 | if self.json_report_file: | |
73 | # We ran the plugin via command line | |
74 | try: | |
75 | fp = open(self.json_report_file) | |
76 | except IOError: | |
77 | self.log('Error opening JSON in the file {}'.format( | |
78 | self.json_report_file | |
79 | ), 'ERROR') | |
80 | else: | |
81 | self.parse_json(fp.read()) | |
82 | if self.remove_report: | |
83 | os.unlink(self.json_report_file) | |
84 | else: | |
85 | # We are importing a report | |
86 | self.parse_json(output) | |
65 | def parseOutputString(self, output): | |
66 | self.parse_json(output) | |
87 | 67 | |
88 | def resolve(self, domain): | |
89 | return socket.gethostbyname(domain) | |
90 | 68 | |
91 | 69 | @property |
92 | 70 | def should_ignore_403(self): |
104 | 82 | return |
105 | 83 | for (base_url, items) in data.items(): |
106 | 84 | base_split = urlparse.urlsplit(base_url) |
107 | ip = self.resolve(base_split.hostname) | |
85 | ip = resolve_hostname(base_split.hostname) | |
108 | 86 | h_id = self.createAndAddHost(ip) |
109 | 87 | |
110 | 88 | i_id = self.createAndAddInterface( |
150 | 128 | parser.add_argument('-h', '--help', action='store_true') |
151 | 129 | parser.add_argument('--json-report') |
152 | 130 | args, unknown = parser.parse_known_args(shlex.split(command_string)) |
153 | ||
154 | 131 | if args.help: |
155 | self.devlog('help detected, ignoring parsing') | |
156 | return command_string | |
132 | return None | |
157 | 133 | if args.json_report: |
158 | 134 | # The user already defined a path to the JSON report |
159 | self.json_report_file = args.json_report | |
160 | self.remove_report = False | |
161 | return command_string | |
135 | self._output_file_path = args.json_report | |
136 | return None | |
162 | 137 | else: |
163 | # Use temporal file to save the report data | |
164 | self.json_report_file = tempfile.mktemp( | |
165 | prefix="dirsearch_report_", suffix=".json") | |
166 | self.devlog('Setting report file to {}'.format( | |
167 | self.json_report_file)) | |
168 | self.remove_report = True | |
169 | return '{} --json-report {}'.format(command_string, | |
170 | self.json_report_file) | |
138 | super().processCommandString(username, current_path, command_string) | |
139 | return '{} --json-report {}'.format(command_string, self._output_file_path) | |
171 | 140 | |
172 | 141 | |
173 | 142 | def createPlugin(): |
17 | 17 | |
18 | 18 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
19 | 19 | |
20 | current_path = os.path.abspath(os.getcwd()) | |
21 | 20 | |
22 | 21 | __author__ = "Francisco Amato" |
23 | 22 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
158 | 157 | self.version = "1.2.2" |
159 | 158 | self.options = None |
160 | 159 | self._current_output = None |
160 | self._use_temp_file = True | |
161 | self._temp_file_extension = "txt" | |
161 | 162 | self._command_regex = re.compile( |
162 | r'^(sudo dnsenum|dnsenum|sudo dnsenum\.pl|dnsenum\.pl|perl dnsenum\.pl|\.\/dnsenum\.pl).*?') | |
163 | ||
163 | r'^(sudo dnsenum|dnsenum|sudo dnsenum\.pl|dnsenum\.pl|perl dnsenum\.pl|\.\/dnsenum\.pl)\s+.*?') | |
164 | self.xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") | |
164 | 165 | |
165 | 166 | def parseOutputString(self, output, debug=False): |
166 | 167 | """ |
183 | 184 | |
184 | 185 | del parser |
185 | 186 | |
186 | xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") | |
187 | ||
188 | 187 | def processCommandString(self, username, current_path, command_string): |
189 | 188 | """ |
190 | 189 | Adds the -oX parameter to get xml output to the command string that the |
191 | 190 | user has set. |
192 | 191 | """ |
193 | ||
192 | super().processCommandString(username, current_path, command_string) | |
194 | 193 | arg_match = self.xml_arg_re.match(command_string) |
195 | 194 | |
196 | 195 | if arg_match is None: |
197 | return re.sub( | |
198 | r"(^.*?dnsenum(\.pl)?)", | |
199 | r"\1 -o %s" % self._output_file_path, | |
200 | command_string) | |
196 | return re.sub(r"(^.*?dnsenum(\.pl)?)", r"\1 -o %s" % self._output_file_path, command_string) | |
201 | 197 | else: |
202 | return re.sub(arg_match.group(1), | |
203 | r"-o %s" % self._output_file_path, | |
204 | command_string) | |
198 | return re.sub(arg_match.group(1), r"-o %s" % self._output_file_path, command_string) | |
205 | 199 | |
206 | 200 | def setHost(self): |
207 | 201 | pass |
210 | 204 | def createPlugin(): |
211 | 205 | return DnsenumPlugin() |
212 | 206 | |
213 | # I'm Py3⏎ | |
207 | # I'm Py3 |
0 | """from __future__ import print_function | |
1 | ||
0 | """ | |
2 | 1 | Faraday Penetration Test IDE |
3 | 2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
4 | 3 | See the file 'doc/LICENSE' for the license information |
5 | 4 | """ |
6 | 5 | from faraday_plugins.plugins.plugin import PluginBase |
7 | 6 | import re |
8 | import os | |
9 | import random | |
10 | 7 | from collections import defaultdict |
11 | 8 | |
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | 9 | |
14 | 10 | __author__ = "Francisco Amato" |
15 | 11 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
102 | 98 | self.version = "0.30" |
103 | 99 | self.options = None |
104 | 100 | self._current_output = None |
105 | self.current_path = None | |
106 | self._command_regex = re.compile(r'^(sudo dnsmap|dnsmap|\.\/dnsmap).*?') | |
101 | self._command_regex = re.compile(r'^(sudo dnsmap|dnsmap|\.\/dnsmap)\s+.*?') | |
107 | 102 | self.xml_arg_re = re.compile(r"^.*(-r\s*[^\s]+).*$") |
103 | self._use_temp_file = True | |
104 | self._temp_file_extension = "txt" | |
108 | 105 | |
109 | 106 | |
110 | 107 | def canParseCommandString(self, current_input): |
128 | 125 | Adds the parameter to get output to the command string that the |
129 | 126 | user has set. |
130 | 127 | """ |
128 | super().processCommandString(username, current_path, command_string) | |
131 | 129 | arg_match = self.xml_arg_re.match(command_string) |
132 | 130 | |
133 | 131 | if arg_match is None: |
7 | 7 | """ |
8 | 8 | from faraday_plugins.plugins.plugin import PluginBase |
9 | 9 | import re |
10 | import os | |
11 | import sys | |
12 | 10 | |
13 | 11 | try: |
14 | 12 | import xml.etree.cElementTree as ET |
20 | 18 | |
21 | 19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
22 | 20 | |
23 | current_path = os.path.abspath(os.getcwd()) | |
24 | 21 | |
25 | 22 | __author__ = "Francisco Amato" |
26 | 23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
171 | 168 | self.options = None |
172 | 169 | self._current_output = None |
173 | 170 | self._command_regex = re.compile( |
174 | r'^(sudo dnsrecon|dnsrecon|sudo dnsrecon\.py|dnsrecon\.py|python dnsrecon\.py|\.\/dnsrecon\.py).*?') | |
171 | r'^(sudo dnsrecon|dnsrecon|sudo dnsrecon\.py|dnsrecon\.py|python dnsrecon\.py|\.\/dnsrecon\.py)\s+.*?') | |
172 | self._use_temp_file = True | |
173 | self._temp_file_extension = "xml" | |
175 | 174 | |
176 | 175 | def validHosts(self, hosts): |
177 | 176 | valid_records = ["NS", "CNAME", "A", "MX", "info"] |
246 | 245 | Adds the -oX parameter to get xml output to the command string that the |
247 | 246 | user has set. |
248 | 247 | """ |
248 | super().processCommandString(username, current_path, command_string) | |
249 | 249 | arg_match = self.xml_arg_re.match(command_string) |
250 | 250 | |
251 | 251 | if arg_match is None: |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | import re | |
7 | import os | |
6 | 8 | |
7 | 9 | from faraday_plugins.plugins.plugin import PluginBase |
8 | import re | |
9 | import os | |
10 | import socket | |
10 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | 11 | |
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | 12 | |
14 | 13 | __author__ = "Francisco Amato" |
15 | 14 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
55 | 54 | line) |
56 | 55 | |
57 | 56 | if mregex is not None: |
58 | ip = self.getAddress(mregex.group(2)) | |
57 | ip = resolve_hostname(mregex.group(2)) | |
59 | 58 | item = { |
60 | 59 | 'host': mregex.group(1), |
61 | 60 | 'ip': ip, |
62 | 61 | 'type': 'info'} |
63 | 62 | self.items.append(item) |
64 | 63 | |
65 | def getAddress(self, hostname): | |
66 | """Returns remote IP address from hostname.""" | |
67 | try: | |
68 | return socket.gethostbyname(hostname) | |
69 | except socket.error: | |
70 | return hostname | |
71 | 64 | |
72 | 65 | |
73 | 66 | class DnswalkPlugin(PluginBase): |
83 | 76 | self.version = "2.0.2" |
84 | 77 | self.options = None |
85 | 78 | self._current_output = None |
86 | self._current_path = None | |
87 | 79 | self._command_regex = re.compile( |
88 | r'^(sudo dnswalk|dnswalk|\.\/dnswalk).*?') | |
80 | r'^(sudo dnswalk|dnswalk|\.\/dnswalk)\s+.*?') | |
89 | 81 | |
90 | global current_path | |
91 | 82 | |
92 | 83 | def canParseCommandString(self, current_input): |
93 | 84 | if self._command_regex.match(current_input.strip()): |
138 | 129 | |
139 | 130 | return True |
140 | 131 | |
141 | def processCommandString(self, username, current_path, command_string): | |
142 | return None | |
143 | ||
144 | 132 | |
145 | 133 | def createPlugin(): |
146 | 134 | return DnswalkPlugin() |
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 | # I'm Py3⏎ |
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 | import re | |
6 | import csv | |
7 | from ast import literal_eval | |
8 | ||
9 | from faraday_plugins.plugins.plugin import PluginCSVFormat | |
10 | ||
11 | ||
12 | class CSVParser: | |
13 | def __init__(self, csv_output, logger): | |
14 | self.logger = logger | |
15 | self.host_data = [ | |
16 | "host_description", | |
17 | "os", | |
18 | "mac", | |
19 | "hostnames", | |
20 | "host_tags" | |
21 | ] | |
22 | self.service_data = [ | |
23 | "service_name", | |
24 | "service_description", | |
25 | "version", | |
26 | "service_status", | |
27 | "service_tags" | |
28 | ] | |
29 | self.vuln_data = [ | |
30 | "name", | |
31 | "desc", | |
32 | "refs", | |
33 | "severity", | |
34 | "resolution", | |
35 | "data", | |
36 | "external_id", | |
37 | "confirmed", | |
38 | "status", | |
39 | "easeofresolution", | |
40 | "impact_confidentiality", | |
41 | "impact_integrity", | |
42 | "impact_availability", | |
43 | "impact_accountability", | |
44 | "policyviolations", | |
45 | "custom_fields", | |
46 | "website", | |
47 | "path", | |
48 | "request", | |
49 | "response", | |
50 | "method", | |
51 | "pname", | |
52 | "params", | |
53 | "query", | |
54 | "status_code", | |
55 | "tags" | |
56 | ] | |
57 | ||
58 | self.items = self.parse_csv(csv_output) | |
59 | ||
60 | def parse_csv(self, output): | |
61 | items = [] | |
62 | reader = csv.DictReader(output, delimiter=',') | |
63 | obj_to_import = self.check_objects_to_import(reader.fieldnames) | |
64 | if not obj_to_import: | |
65 | return items | |
66 | ||
67 | if 'ip' in reader.fieldnames and 'target' not in reader.fieldnames: | |
68 | index = reader.fieldnames.index('ip') | |
69 | reader.fieldnames[index] = "target" | |
70 | custom_fields_names = self.get_custom_fields_names(reader.fieldnames) | |
71 | for row in reader: | |
72 | self.data = {} | |
73 | self.data['row_with_service'] = False | |
74 | self.data['row_with_vuln'] = False | |
75 | self.build_host(row) | |
76 | if "service" in obj_to_import: | |
77 | if row['port'] and row['protocol']: | |
78 | self.data['row_with_service'] = True | |
79 | self.build_service(row) | |
80 | else: | |
81 | self.data['row_with_service'] = False | |
82 | if "vuln" in obj_to_import: | |
83 | if row['name'] and row['desc']: | |
84 | self.data['row_with_vuln'] = True | |
85 | self.build_vulnerability(row, custom_fields_names) | |
86 | else: | |
87 | self.data['row_with_service'] = False | |
88 | ||
89 | items.append(self.data) | |
90 | return items | |
91 | ||
92 | def check_objects_to_import(self, headers): | |
93 | obj_to_import = [] | |
94 | ||
95 | # From valid_headers, Faraday will define which objects to import | |
96 | valid_headers = [ | |
97 | "ip", | |
98 | "port", "protocol", | |
99 | "name", "desc", "target" | |
100 | ] | |
101 | ||
102 | matching_headers = set(valid_headers) & set(headers) | |
103 | ||
104 | if "ip" not in matching_headers and "target" not in matching_headers: | |
105 | self.logger.error("No host specified. Please, specify at least one host.") | |
106 | return None | |
107 | ||
108 | if "ip" in matching_headers: | |
109 | # Remove ip field to leave only target field | |
110 | matching_headers.remove("ip") | |
111 | if "target" not in matching_headers: | |
112 | matching_headers.add("target") | |
113 | ||
114 | obj_to_import.append('host') | |
115 | ||
116 | if "port" in matching_headers or "protocol" in matching_headers: | |
117 | port = True if "port" in matching_headers else False | |
118 | protocol = True if "protocol" in matching_headers else False | |
119 | ||
120 | if (port and not protocol) or (protocol and not port): | |
121 | self.logger.error( | |
122 | ("Missing columns in CSV file. " | |
123 | "In order to import services, you need to add a column called port " | |
124 | " and a column called protocol.") | |
125 | ) | |
126 | return None | |
127 | else: | |
128 | obj_to_import.append('service') | |
129 | ||
130 | if "name" in matching_headers or "desc" in matching_headers: | |
131 | vuln_name = True if "name" in matching_headers else False | |
132 | vuln_desc = True if "desc" in matching_headers else False | |
133 | ||
134 | if (vuln_name and not vuln_desc) or (vuln_desc and not vuln_name): | |
135 | self.logger.error( | |
136 | ("Missing columns in CSV file. " | |
137 | "In order to import vulnerabilities, you need to add a " | |
138 | "column called name and a column called desc.") | |
139 | ) | |
140 | return None | |
141 | else: | |
142 | obj_to_import.append('vuln') | |
143 | ||
144 | return obj_to_import | |
145 | ||
146 | def get_custom_fields_names(self, headers): | |
147 | custom_fields_names = [] | |
148 | for header in headers: | |
149 | match = re.match(r"cf_(\w+)", header) | |
150 | if match: | |
151 | custom_fields_names.append(match.group(1)) | |
152 | ||
153 | return custom_fields_names | |
154 | ||
155 | def build_host(self, row): | |
156 | self.data['target'] = row['target'] | |
157 | for item in self.host_data: | |
158 | if item == "hostnames": | |
159 | self.data[item] = self.build_hostnames_list(row) | |
160 | continue | |
161 | ||
162 | if item in row: | |
163 | if item == "host_tags": | |
164 | self.data[item] = literal_eval(row[item]) | |
165 | else: | |
166 | self.data[item] = row[item] | |
167 | else: | |
168 | self.data[item] = None | |
169 | ||
170 | def build_service(self, row): | |
171 | self.data['port'] = row['port'] | |
172 | self.data['protocol'] = row['protocol'] | |
173 | for item in self.service_data: | |
174 | if item in row: | |
175 | if item == 'service_status': | |
176 | if row[item] == '': | |
177 | # If status is not specified, set it as 'open' | |
178 | self.data[item] = "open" | |
179 | continue | |
180 | elif item == 'service_tags': | |
181 | self.data[item] = literal_eval(row[item]) | |
182 | continue | |
183 | self.data[item] = row[item] | |
184 | else: | |
185 | self.data[item] = None | |
186 | ||
187 | def build_vulnerability(self, row, custom_fields_names): | |
188 | self.data['vuln_name'] = row['name'] | |
189 | self.data['vuln_desc'] = row['desc'] | |
190 | impact_dict = { | |
191 | "accountability": False, | |
192 | "confidentiality": False, | |
193 | "availability": False, | |
194 | "integrity": False, | |
195 | } | |
196 | ||
197 | if "web_vulnerability" in row: | |
198 | self.data['web_vulnerability'] = True if row['web_vulnerability'] == "True" else False | |
199 | else: | |
200 | self.data['web_vulnerability'] = False | |
201 | ||
202 | for item in self.vuln_data: | |
203 | if item in row: | |
204 | if "impact_" in item: | |
205 | impact = re.match(r"impact_(\w+)", item).group(1) | |
206 | impact_dict[impact] = True if row[item] == "True" else False | |
207 | elif item in ["refs", "policyviolations", "tags"]: | |
208 | self.data[item] = literal_eval(row[item]) | |
209 | else: | |
210 | self.data[item] = row[item] | |
211 | else: | |
212 | self.data[item] = None | |
213 | ||
214 | self.data['impact'] = impact_dict | |
215 | self.data['custom_fields'] = self.parse_custom_fields(row, custom_fields_names) | |
216 | ||
217 | def build_hostnames_list(self, row): | |
218 | hostnames = [] | |
219 | if "hostnames" in row: | |
220 | try: | |
221 | hostnames = literal_eval(row['hostnames']) | |
222 | except (ValueError, SyntaxError): | |
223 | self.logger.error("Hostname not valid. Faraday will set it as empty.") | |
224 | return hostnames | |
225 | ||
226 | def parse_vuln_impact(self, impact): | |
227 | impacts = [ | |
228 | "accountability", | |
229 | "confidentiality", | |
230 | "availability", | |
231 | "integrity" | |
232 | ] | |
233 | for item in impacts: | |
234 | if item in impact: | |
235 | return item | |
236 | ||
237 | def parse_custom_fields(self, row, custom_fields_names): | |
238 | custom_fields = {} | |
239 | for cf_name in custom_fields_names: | |
240 | cf_value = row["cf_" + cf_name] | |
241 | try: | |
242 | custom_fields[cf_name] = literal_eval(cf_value) | |
243 | except (ValueError, SyntaxError): | |
244 | custom_fields[cf_name] = cf_value | |
245 | ||
246 | return custom_fields | |
247 | ||
248 | ||
249 | class FaradayCSVPlugin(PluginCSVFormat): | |
250 | def __init__(self): | |
251 | super().__init__() | |
252 | self.id = "faraday_csv" | |
253 | self.name = "Faraday CSV Plugin" | |
254 | self.plugin_version = "1.0" | |
255 | self.csv_headers = [{'ip'}, {'target'}] | |
256 | ||
257 | def _parse_filename(self, filename): | |
258 | with open(filename, **self.open_options) as output: | |
259 | self.parseOutputString(output) | |
260 | ||
261 | def parseOutputString(self, output, debug=False): | |
262 | parser = CSVParser(output, self.logger) | |
263 | ||
264 | for item in parser.items: | |
265 | h_id = self.createAndAddHost( | |
266 | name=item['target'], | |
267 | os=item['os'], | |
268 | hostnames=item['hostnames'], | |
269 | mac=item['mac'], | |
270 | description=item['host_description'] or "", | |
271 | tags=item['host_tags'] | |
272 | ) | |
273 | s_id = None | |
274 | if item['row_with_service']: | |
275 | s_id = self.createAndAddServiceToHost( | |
276 | h_id, | |
277 | name=item['service_name'], | |
278 | protocol=item['protocol'], | |
279 | ports=item['port'], | |
280 | status=item['service_status'] or None, | |
281 | version=item['version'], | |
282 | description=item['service_description'], | |
283 | tags=item['service_tags'] | |
284 | ) | |
285 | if item['row_with_vuln']: | |
286 | if not item['web_vulnerability'] and not s_id: | |
287 | self.createAndAddVulnToHost( | |
288 | h_id, | |
289 | name=item['vuln_name'], | |
290 | desc=item['vuln_desc'], | |
291 | ref=item['refs'], | |
292 | severity=item['severity'], | |
293 | resolution=item['resolution'], | |
294 | data=item['data'], | |
295 | external_id=item['external_id'], | |
296 | confirmed=item['confirmed'] or False, | |
297 | status=item['status'] or "", | |
298 | easeofresolution=item['easeofresolution'] or None, | |
299 | impact=item['impact'], | |
300 | policyviolations=item['policyviolations'], | |
301 | custom_fields=item['custom_fields'], | |
302 | tags=item['tags'] | |
303 | ) | |
304 | if not item['web_vulnerability'] and s_id: | |
305 | self.createAndAddVulnToService( | |
306 | h_id, | |
307 | s_id, | |
308 | name=item['vuln_name'], | |
309 | desc=item['vuln_desc'], | |
310 | ref=item['refs'], | |
311 | severity=item['severity'], | |
312 | resolution=item['resolution'], | |
313 | data=item['data'], | |
314 | external_id=item['external_id'], | |
315 | confirmed=item['confirmed'] or False, | |
316 | status=item['status'] or "", | |
317 | easeofresolution=item['easeofresolution'] or None, | |
318 | impact=item['impact'], | |
319 | policyviolations=item['policyviolations'], | |
320 | custom_fields=item['custom_fields'], | |
321 | tags=item['tags'] | |
322 | ) | |
323 | elif item['web_vulnerability']: | |
324 | self.createAndAddVulnWebToService( | |
325 | h_id, | |
326 | s_id, | |
327 | name=item['vuln_name'], | |
328 | desc=item['vuln_desc'], | |
329 | ref=item['refs'], | |
330 | severity=item['severity'], | |
331 | resolution=item['resolution'], | |
332 | website=item['website'], | |
333 | path=item['path'], | |
334 | request=item['request'], | |
335 | response=item['response'], | |
336 | method=item['method'], | |
337 | pname=item['pname'], | |
338 | params=item['params'], | |
339 | query=item['query'], | |
340 | data=item['data'], | |
341 | external_id=item['external_id'], | |
342 | confirmed=item['confirmed'] or False, | |
343 | status=item['status'] or "", | |
344 | easeofresolution=item['easeofresolution'] or None, | |
345 | impact=item['impact'], | |
346 | policyviolations=item['policyviolations'], | |
347 | status_code=item['status_code'] or None, | |
348 | custom_fields=item['custom_fields'], | |
349 | tags=item['tags'] | |
350 | ) | |
351 | ||
352 | ||
353 | def createPlugin(): | |
354 | return FaradayCSVPlugin() |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | from faraday_plugins.plugins.plugin import PluginBase | |
7 | import socket | |
8 | 6 | import re |
9 | 7 | import os |
10 | 8 | import random |
11 | 9 | |
12 | current_path = os.path.abspath(os.getcwd()) | |
10 | from faraday_plugins.plugins.plugin import PluginBase | |
11 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
12 | ||
13 | 13 | |
14 | 14 | __author__ = "Francisco Amato" |
15 | 15 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
108 | 108 | self.version = "0.9.9" |
109 | 109 | self.options = None |
110 | 110 | self._current_output = None |
111 | self._current_path = None | |
112 | 111 | self._command_regex = re.compile( |
113 | r'^(sudo fierce|fierce|sudo fierce\.pl|fierce\.pl|perl fierce\.pl|\.\/fierce\.pl).*?') | |
114 | global current_path | |
112 | r'^(sudo fierce|fierce|sudo fierce\.pl|fierce\.pl|perl fierce\.pl|\.\/fierce\.pl)\s+.*?') | |
115 | 113 | |
116 | self.xml_arg_re = re.compile(r"^.*(>\s*[^\s]+).*$") | |
117 | ||
118 | def canParseCommandString(self, current_input): | |
119 | if self._command_regex.match(current_input.strip()): | |
120 | return True | |
121 | else: | |
122 | return False | |
123 | 114 | |
124 | 115 | def resolveCNAME(self, item, items): |
125 | 116 | for i in items: |
127 | 118 | item['ip'] = i['ip'] |
128 | 119 | return item |
129 | 120 | try: |
130 | item['ip'] = socket.gethostbyname(item['ip']) | |
121 | item['ip'] = resolve_hostname(item['ip']) | |
131 | 122 | except: |
132 | 123 | pass |
133 | 124 | return item |
135 | 126 | def resolveNS(self, item, items): |
136 | 127 | try: |
137 | 128 | item['hosts'][0] = item['ip'] |
138 | item['ip'] = socket.gethostbyname(item['ip']) | |
129 | item['ip'] = resolve_hostname(item['ip']) | |
139 | 130 | except: |
140 | 131 | pass |
141 | 132 | return item |
182 | 173 | desc="A Dns server allows unrestricted zone transfers", |
183 | 174 | ref=["CVE-1999-0532"]) |
184 | 175 | |
185 | def processCommandString(self, username, current_path, command_string): | |
186 | self._output_file_path = os.path.join( | |
187 | self.data_path, | |
188 | "%s_%s_output-%s.txt" % ( | |
189 | self.get_ws(), | |
190 | self.id, | |
191 | random.uniform(1, 10)) | |
192 | ) | |
193 | ||
194 | arg_match = self.xml_arg_re.match(command_string) | |
195 | ||
196 | if arg_match is None: | |
197 | return "%s > %s" % (command_string, self._output_file_path) | |
198 | else: | |
199 | return re.sub(arg_match.group(1), | |
200 | r"> %s" % self._output_file_path, | |
201 | command_string) | |
202 | 176 | |
203 | 177 | |
204 | 178 | def createPlugin(): |
0 | 0 | import base64 |
1 | 1 | import io |
2 | 2 | import re |
3 | from html.parser import HTMLParser | |
3 | import html | |
4 | 4 | from zipfile import ZipFile |
5 | 5 | |
6 | 6 | import html2text |
26 | 26 | for host in fp.hosts.keys(): |
27 | 27 | fp.hosts[host] = self.createAndAddHost(host) |
28 | 28 | |
29 | for vuln in fp.vulns.keys(): | |
29 | for vuln_key, vuln in fp.vulns.items(): | |
30 | 30 | self.createAndAddVulnToHost( |
31 | host_id=fp.hosts[fp.vulns[vuln]['host']], | |
32 | name=fp.vulns[vuln]['name'], | |
33 | desc=fp.format_description(vuln), | |
34 | ref=fp.descriptions[fp.vulns[vuln]['class']]['references'], | |
35 | severity=fp.vulns[vuln]['severity'], | |
31 | host_id=fp.hosts[vuln['host']], | |
32 | name=vuln['name'], | |
33 | desc=fp.format_description(vuln_key), | |
34 | ref=fp.descriptions[vuln['class']]['references'], | |
35 | severity=vuln['severity'], | |
36 | 36 | resolution="", |
37 | 37 | data="", |
38 | external_id=vuln.text | |
38 | external_id=vuln_key.text | |
39 | 39 | ) |
40 | 40 | |
41 | 41 | def _process_webinspect_vulns(self, fp): |
330 | 330 | if self.fvdl is None: |
331 | 331 | return |
332 | 332 | for description in self.fvdl.Description: |
333 | ||
334 | self.descriptions[description.get("classID")] = {} | |
335 | ||
336 | if description.get('classID') not in self.vuln_classes: | |
333 | class_id = description.get("classID") | |
334 | self.descriptions[class_id] = {} | |
335 | if class_id not in self.vuln_classes: | |
337 | 336 | continue |
338 | ||
339 | tips = "" | |
340 | 337 | if hasattr(description, 'Tips'): |
341 | for tip in description.Tips.getchildren(): | |
342 | tips += "\n" + tip.text | |
343 | ||
344 | htmlparser = HTMLParser() | |
345 | self.descriptions[description.get("classID")]['text'] = htmlparser.unescape( | |
346 | "Summary:\n{}\n\nExplanation:\n{}\n\nRecommendations:\n{}\n\nTips:{}".format( | |
347 | description.Abstract, description.Explanation, description.Recommendations, tips)) | |
348 | ||
338 | tips = "\n".join(map(lambda x: x.text, description.Tips.getchildren())) | |
339 | else: | |
340 | tips = "" | |
341 | text = f"Summary:\n{description.Abstract}\n\nExplanation:\n{description.Explanation}\n\nRecommendations:\n{description.Recommendations}\n\nTips:{tips}" | |
342 | self.descriptions[description.get("classID")]['text'] = html.unescape(text) | |
349 | 343 | # group vuln references |
350 | 344 | references = [] |
351 | try: | |
352 | children = description.References.getchildren() | |
353 | except AttributeError: | |
354 | children = [] | |
355 | ||
356 | for reference in children: | |
357 | ||
358 | for attr in dir(reference): | |
359 | if attr == '__class__': | |
360 | break | |
361 | ||
362 | references.append("{}: {}\n".format(attr, getattr(reference, attr))) | |
363 | ||
345 | if hasattr(description, "References"): | |
346 | references_elements = description.References.getchildren() | |
347 | for reference in references_elements: | |
348 | for children in reference.getchildren(): | |
349 | name = children.tag.split("}")[1] | |
350 | value = children.text | |
351 | references.append(f"{name}: {value}") | |
364 | 352 | self.descriptions[description.get("classID")]['references'] = references |
365 | 353 | |
366 | 354 | def format_description(self, vulnID): |
34 | 34 | self._current_output = None |
35 | 35 | self.target = None |
36 | 36 | |
37 | self._command_regex = re.compile( | |
38 | r'^(fruitywifi).*?') | |
37 | self._command_regex = re.compile(r'^(fruitywifi)\s+.*?') | |
39 | 38 | |
40 | 39 | self.addSetting("Token", str, "e5dab9a69988dd65e578041416773149ea57a054") |
41 | 40 | self.addSetting("Server", str, "http://127.0.0.1:8000") |
79 | 78 | |
80 | 79 | def parseOutputString(self, output, debug=False): |
81 | 80 | |
82 | try: | |
81 | try: | |
83 | 82 | output = json.loads(output) |
84 | 83 | |
85 | 84 | if len(output) > 0: |
128 | 127 | else: |
129 | 128 | return False |
130 | 129 | |
131 | def processCommandString(self, username, current_path, command_string, debug=False): | |
130 | def processCommandString(self, username, current_path, command_string): | |
132 | 131 | """ |
133 | 132 | """ |
134 | #params = command_string.replace("fruitywifi","") | |
133 | super().processCommandString(username, current_path, command_string) | |
135 | 134 | params = "-t %s -s %s" % (self.getSetting("Token"), self.getSetting("Server")) |
136 | 135 | |
137 | 136 | return "python " + os.path.dirname(__file__) + "/fruitywifi.py " + params |
138 | #return None | |
137 | ||
139 | 138 | |
140 | 139 | |
141 | 140 | def createPlugin(): |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | from faraday_plugins.plugins.plugin import PluginBase | |
7 | 6 | import re |
8 | 7 | import os |
9 | import socket | |
10 | 8 | |
11 | current_path = os.path.abspath(os.getcwd()) | |
9 | from faraday_plugins.plugins.plugin import PluginBase | |
10 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | ||
12 | 12 | |
13 | 13 | __author__ = "Javier Victor Mariano Bruno" |
14 | 14 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
35 | 35 | self.framework_version = "1.0.0" |
36 | 36 | self.options = None |
37 | 37 | self._current_output = None |
38 | self._command_regex = re.compile(r'^ftp.*?') | |
38 | self._command_regex = re.compile(r'^ftp\s+.*?') | |
39 | 39 | self._host_ip = None |
40 | 40 | self._port = "21" |
41 | 41 | self._info = 0 |
42 | 42 | self._version = None |
43 | 43 | |
44 | global current_path | |
45 | 44 | |
46 | def resolve(self, host): | |
47 | try: | |
48 | return socket.gethostbyname(host) | |
49 | except: | |
50 | pass | |
51 | return host | |
52 | 45 | |
53 | 46 | def parseOutputString(self, output, debug=False): |
54 | 47 | |
56 | 49 | banner = re.search("220?([\w\W]+)$", output) |
57 | 50 | if re.search("Connection timed out", output) is None and host_info is not None: |
58 | 51 | hostname = host_info.group(1) |
59 | ip_address = self.resolve(hostname) | |
52 | ip_address = resolve_hostname(hostname) | |
60 | 53 | self._version = banner.groups(0) if banner else "" |
61 | 54 | if debug: |
62 | 55 | print(ip_address) |
85 | 78 | def processCommandString(self, username, current_path, command_string): |
86 | 79 | """ |
87 | 80 | """ |
81 | super().processCommandString(username, current_path, command_string) | |
88 | 82 | count_args = command_string.split() |
89 | ||
90 | 83 | c = count_args.__len__() |
91 | 84 | self._port = "21" |
92 | 85 | if re.search("[\d]+", count_args[c - 1]): |
2 | 2 | Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | from faraday_plugins.plugins.plugin import PluginBase | |
6 | import socket | |
7 | 5 | import re |
8 | 6 | import os |
9 | 7 | |
10 | current_path = os.path.abspath(os.getcwd()) | |
8 | from faraday_plugins.plugins.plugin import PluginBase | |
9 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | 10 | |
12 | 11 | __author__ = "Francisco Amato" |
13 | 12 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
41 | 40 | self.add_host_info_to_items(item['ip'], item['host']) |
42 | 41 | elif goohost_scantype == 'host': |
43 | 42 | data = line.strip() |
44 | item = {'host': data, 'ip': self.resolve(data)} | |
43 | item = {'host': data, 'ip': resolve_hostname(data)} | |
45 | 44 | self.add_host_info_to_items(item['ip'], item['host']) |
46 | 45 | else: |
47 | 46 | item = {'data': line} |
48 | ||
49 | def resolve(self, host): | |
50 | try: | |
51 | return socket.gethostbyname(host) | |
52 | except: | |
53 | pass | |
54 | return host | |
55 | 47 | |
56 | 48 | def add_host_info_to_items(self, ip_address, hostname): |
57 | 49 | data = {} |
79 | 71 | self.plugin_version = "0.0.1" |
80 | 72 | self.version = "v.0.0.1" |
81 | 73 | self.options = None |
82 | self._current_output = None | |
83 | self._current_path = None | |
84 | self._command_regex = re.compile( | |
85 | r'^(sudo goohost\.sh|goohost\.sh|sh goohost\.sh|\.\/goohost\.sh).*?') | |
74 | self._command_regex = re.compile(r'^(sudo goohost\.sh|goohost\.sh|sh goohost\.sh|\.\/goohost\.sh)\s+.*?') | |
86 | 75 | self.host = None |
87 | ||
88 | global current_path | |
89 | self.output_path = None | |
90 | 76 | self._command_string = None |
91 | 77 | |
92 | def parseOutputString(self, output, debug=False): | |
78 | def parseOutputString(self, output): | |
93 | 79 | """ |
94 | 80 | This method will check if the import was made through the console or by importing a Goohost report. |
95 | 81 | |
100 | 86 | |
101 | 87 | self.scantype defines the method used to generate the Goohost report |
102 | 88 | |
103 | NOTE: if 'debug' is true then it is being run from a test case and the | |
104 | output being sent is valid. | |
105 | 89 | """ |
106 | ||
107 | if self._command_string: | |
108 | # Import from console | |
109 | self.scantype = self.define_scantype_by_command(self._command_string) | |
110 | report_output = output | |
111 | output = self.read_output_file(report_output) | |
112 | else: | |
113 | # Import from report | |
114 | self.scantype = self.define_scantype_by_output(output) | |
115 | ||
116 | if debug: | |
117 | parser = GoohostParser(output, self.scantype) | |
118 | else: | |
119 | parser = GoohostParser(output, self.scantype) | |
120 | if self.scantype == 'host' or self.scantype == 'ip': | |
121 | for item in parser.items: | |
122 | h_id = self.createAndAddHost( | |
123 | item['ip'], | |
124 | hostnames=item['hosts']) | |
125 | ||
90 | scantype = self.define_scantype_by_output(output) | |
91 | parser = GoohostParser(output, scantype) | |
92 | if scantype == 'host' or scantype == 'ip': | |
93 | for item in parser.items: | |
94 | h_id = self.createAndAddHost(item['ip'], hostnames=item['hosts']) | |
126 | 95 | del parser |
127 | ||
128 | def processCommandString(self, username, current_path, command_string): | |
129 | """ | |
130 | Set output path for parser... | |
131 | """ | |
132 | self._current_path = current_path | |
133 | self._command_string = command_string | |
134 | 96 | |
135 | 97 | def define_scantype_by_command(self, command): |
136 | 98 | method_regex = re.compile(r'-m (mail|host|ip)') |
137 | 99 | method = method_regex.search(command) |
138 | 100 | if method: |
139 | 101 | return method.group(1) |
140 | ||
141 | 102 | return 'host' |
142 | 103 | |
143 | 104 | def define_scantype_by_output(self, output): |
144 | 105 | lines = output.split('\n') |
145 | 106 | line = lines[0].split(' ') |
146 | ||
147 | 107 | if len(line) == 1: |
148 | 108 | return 'host' |
149 | 109 | elif len(line) == 2: |
150 | 110 | return 'ip' |
151 | 111 | |
152 | def read_output_file(self, report_path): | |
153 | mypath = re.search("Results saved in file (\S+)", report_path) | |
154 | if not mypath: | |
112 | def get_report_path_from_output(self, command_output): | |
113 | report_name = re.search("Results saved in file (\S+)", command_output) | |
114 | if not report_name: | |
155 | 115 | return False |
156 | 116 | else: |
157 | self.output_path = self._current_path + "/" + mypath.group(1) | |
158 | if not os.path.exists(self.output_path): | |
117 | self._output_file_path = os.path.join(self._current_path, report_name.group(1)) | |
118 | if not os.path.exists(self._output_file_path): | |
159 | 119 | return False |
160 | with open(self.output_path, 'r') as report: | |
161 | output = report.read() | |
120 | else: | |
121 | self._delete_temp_file = True | |
162 | 122 | |
163 | return output | |
123 | def processOutput(self, command_output): | |
124 | self.get_report_path_from_output(command_output) | |
125 | if self.has_custom_output(): | |
126 | self._parse_filename(self.get_custom_file_path()) | |
127 | else: | |
128 | self.parseOutputString(command_output) | |
164 | 129 | |
165 | 130 | |
166 | 131 | def createPlugin(): |
167 | 132 | return GoohostPlugin() |
168 | 133 | |
169 | # I'm Py3 |
36 | 36 | ip_address = regex_ipv4.group(0).rstrip("):") # Regex pls |
37 | 37 | else: |
38 | 38 | # Exit plugin, ip address not found. bad output |
39 | self.log("Abort plugin: Ip address not found", "INFO") | |
39 | self.logger.warning("Abort plugin: Ip address not found", "INFO") | |
40 | 40 | return |
41 | 41 | |
42 | 42 | hostname = output.split(" ")[1] |
69 | 69 | s_id = self.createAndAddServiceToInterface( |
70 | 70 | host_id, i_id, service, protocol="tcp", ports=port, status="open") |
71 | 71 | |
72 | def processCommandString(self, username, current_path, command_string): | |
73 | return None | |
74 | ||
75 | 72 | |
76 | 73 | def createPlugin(): |
77 | 74 | return hping3() |
4 | 4 | """ |
5 | 5 | from faraday_plugins.plugins.plugin import PluginBase |
6 | 6 | import re |
7 | import os | |
8 | import random | |
9 | ||
10 | current_path = os.path.abspath(os.getcwd()) | |
11 | 7 | |
12 | 8 | __author__ = "Francisco Amato" |
13 | 9 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
29 | 25 | def __init__(self, xml_output): |
30 | 26 | lines = xml_output.splitlines() |
31 | 27 | self.items = [] |
32 | for l in lines: | |
28 | for line in lines: | |
33 | 29 | |
34 | 30 | reg = re.search( |
35 | 31 | "\[([^$]+)\]\[([^$]+)\] host: ([^$]+) login: ([^$]+) password: ([^$]+)", |
36 | l) | |
32 | line) | |
37 | 33 | |
38 | 34 | if reg: |
39 | ||
40 | 35 | item = { |
41 | 36 | 'port': reg.group(1), |
42 | 37 | 'plugin': reg.group(2), |
59 | 54 | self.plugin_version = "0.0.1" |
60 | 55 | self.version = "7.5" |
61 | 56 | self.options = None |
62 | self._current_output = None | |
63 | self._current_path = None | |
64 | self._command_regex = re.compile( | |
65 | r'^(sudo hydra|sudo \.\/hydra|hydra|\.\/hydra).*?') | |
57 | self._command_regex = re.compile(r'^(sudo hydra|sudo \.\/hydra|hydra|\.\/hydra)\s+.*?') | |
66 | 58 | self.host = None |
67 | ||
59 | self._use_temp_file = True | |
60 | self._temp_file_extension = "txt" | |
61 | self.xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") | |
68 | 62 | |
69 | 63 | def parseOutputString(self, output, debug=False): |
70 | 64 | """ |
133 | 127 | |
134 | 128 | del parser |
135 | 129 | |
136 | xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") | |
137 | ||
138 | 130 | def processCommandString(self, username, current_path, command_string): |
139 | ||
140 | self._output_file_path = os.path.join( | |
141 | self.data_path, | |
142 | "hydra_output-%s.txt" % random.uniform(1, 10)) | |
143 | ||
131 | super().processCommandString(username, current_path, command_string) | |
144 | 132 | arg_match = self.xml_arg_re.match(command_string) |
145 | ||
146 | 133 | if arg_match is None: |
147 | 134 | return re.sub(r"(^.*?hydra?)", r"\1 -o %s" % self._output_file_path, command_string) |
148 | 135 | else: |
149 | return re.sub( | |
150 | arg_match.group(1), | |
151 | r"-o %s" % self._output_file_path, | |
152 | command_string) | |
136 | return re.sub(arg_match.group(1), r"-o %s" % self._output_file_path, command_string) | |
153 | 137 | |
154 | 138 | def _isIPV4(self, ip): |
155 | 139 | if len(ip.split(".")) == 4: |
4 | 4 | |
5 | 5 | """ |
6 | 6 | import re |
7 | import os | |
8 | 7 | |
9 | 8 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
10 | 9 | try: |
17 | 16 | |
18 | 17 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
19 | 18 | |
20 | current_path = os.path.abspath(os.getcwd()) | |
21 | 19 | |
22 | 20 | __author__ = "Francisco Amato" |
23 | 21 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
59 | 57 | try: |
60 | 58 | tree = ET.fromstring(xml_output) |
61 | 59 | except SyntaxError as err: |
62 | #logger.error("SyntaxError: %s. %s" % (err, xml_output)) | |
63 | 60 | return None |
64 | 61 | |
65 | 62 | return tree |
224 | 221 | self.version = "Core Impact 2013R1/2017R2" |
225 | 222 | self.framework_version = "1.0.0" |
226 | 223 | self.options = None |
227 | self._current_output = None | |
228 | self._command_regex = re.compile(r'^(sudo impact|\.\/impact).*?') | |
229 | 224 | |
230 | 225 | def parseOutputString(self, output, debug=False): |
231 | 226 | parser = ImpactXmlParser(output) |
277 | 272 | ref=v.ref) |
278 | 273 | else: |
279 | 274 | s_id = mapped_services.get(v.service_name) or mapped_ports.get(v.port) |
280 | print(v.service_name) | |
281 | print(s_id) | |
282 | 275 | self.createAndAddVulnToService( |
283 | 276 | h_id, |
284 | 277 | s_id, |
297 | 290 | status=p['status']) |
298 | 291 | del parser |
299 | 292 | |
300 | def processCommandString(self, username, current_path, command_string): | |
301 | return None | |
302 | 293 | |
303 | 294 | def setHost(self): |
304 | 295 | pass |
307 | 298 | def createPlugin(): |
308 | 299 | return ImpactPlugin() |
309 | 300 | |
310 | # I'm Py3 | |
301 |
5 | 5 | """ |
6 | 6 | import os |
7 | 7 | from lxml import etree |
8 | from faraday_plugins.plugins.plugin import PluginBase | |
8 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
9 | 9 | |
10 | 10 | |
11 | 11 | try: |
116 | 116 | return None |
117 | 117 | |
118 | 118 | |
119 | class JunitPlugin(PluginBase): | |
119 | class JunitPlugin(PluginXMLFormat): | |
120 | 120 | """ |
121 | 121 | Example plugin to parse junit output. |
122 | 122 | """ |
130 | 130 | self.framework_version = "1.0.0" |
131 | 131 | self.options = None |
132 | 132 | self._current_output = None |
133 | self._command_regex = None | |
134 | 133 | |
135 | 134 | def parseOutputString(self, output, debug=False): |
136 | 135 |
10 | 10 | from faraday_plugins.plugins.plugins_utils import filter_services, get_all_protocols |
11 | 11 | |
12 | 12 | |
13 | current_path = os.path.abspath(os.getcwd()) | |
14 | ||
15 | ||
16 | 13 | class LynisLogDataExtracter(): |
17 | def __init__(self, datfile=None, output=None): | |
14 | def __init__(self, output): | |
18 | 15 | self.services = defaultdict(list) |
19 | if datfile and os.path.exists(datfile): | |
20 | with open(datfile) as f: | |
21 | self.rawcontents = f.read() | |
22 | ||
23 | if output: | |
24 | self.rawcontents = output | |
16 | self.rawcontents = output | |
25 | 17 | |
26 | 18 | def _svcHelper(self, ip, port, protocol, name): |
27 | 19 | self.services[ip].append({'port': port, 'protocol': protocol, 'name': name}) |
32 | 24 | domain_match = re.search('^domainname=(.+)$', self.rawcontents, re.MULTILINE) |
33 | 25 | if domain_match: |
34 | 26 | domain = domain_match.group(1).strip() |
35 | return ".".join([hostname,domain]) | |
27 | return f"{hostname}.{domain}" | |
36 | 28 | else: |
37 | 29 | return hostname |
38 | 30 | |
44 | 36 | return " ".join([name, version]) |
45 | 37 | |
46 | 38 | def ipv4(self): |
47 | ipv4addrs = [] | |
48 | 39 | ipv4s = re.findall('^network_ipv4_address\[\]=(.+)$', |
49 | 40 | self.rawcontents, re.MULTILINE) |
50 | 41 | ipv4addrs = self.ipv4_filter(ipv4s) |
51 | 42 | return(ipv4addrs) |
52 | 43 | |
53 | 44 | def ipv6(self): |
54 | ipv6addrs = [] | |
55 | 45 | ipv6s = re.findall('^network_ipv6_address\[\]=(.+)$', |
56 | 46 | self.rawcontents, re.MULTILINE) |
57 | 47 | ipv6addrs = self.ipv6_filter(ipv6s) |
75 | 65 | |
76 | 66 | def kernelVersion(self): |
77 | 67 | versions_dict = {} |
78 | ||
79 | version = re.search('^os_kernel_version=(.+)$', | |
80 | self.rawcontents, re.MULTILINE) | |
68 | version = re.search('^os_kernel_version=(.+)$', self.rawcontents, re.MULTILINE) | |
81 | 69 | if version: |
82 | 70 | versions_dict['Kernel Version'] = version.group(1).strip() |
83 | ||
84 | version_full = re.search('^os_kernel_version_full=(.+)$', | |
85 | self.rawcontents, re.MULTILINE) | |
71 | version_full = re.search('^os_kernel_version_full=(.+)$', self.rawcontents, re.MULTILINE) | |
86 | 72 | if version_full: |
87 | 73 | versions_dict['Kernel Version Full'] = version_full.group(1).strip() |
88 | ||
89 | 74 | return versions_dict |
90 | 75 | |
91 | 76 | def listeningservices(self): |
237 | 222 | |
238 | 223 | def __init__(self): |
239 | 224 | super().__init__() |
240 | self.extension = [".dat", ".log"] | |
241 | 225 | self.id = "Lynis" |
242 | 226 | self.name = "Lynis DAT Output Plugin" |
243 | 227 | self.plugin_version = "0.4" |
244 | 228 | self.version = "2.7.1" |
245 | 229 | self.options = None |
246 | self._current_output = None | |
247 | rr = r'^(lynis|sudo lynis|\.\/lynis|sudo \.\/lynis).*?' | |
248 | self._command_regex = re.compile(rr) | |
230 | self._command_regex = re.compile(r'^(lynis|\.\/lynis|)\s+.*?') | |
249 | 231 | self._hosts = [] |
250 | ||
251 | global current_path | |
232 | self.extension = [".dat", ".log"] | |
252 | 233 | |
253 | 234 | def report_belongs_to(self, **kwargs): |
254 | 235 | if super().report_belongs_to(**kwargs): |
258 | 239 | return output.startswith("# Lynis Report") |
259 | 240 | return False |
260 | 241 | |
261 | def parseOutputString(self, output, debug=False): | |
262 | datpath = self.getDatPath(output) | |
263 | ||
264 | if datpath: | |
265 | lde = LynisLogDataExtracter(datfile=datpath) | |
266 | elif '# Lynis Report' in output: | |
267 | lde = LynisLogDataExtracter(output=output) | |
242 | def parseOutputString(self, output): | |
243 | """ | |
244 | Lynis does not have a means to specify the location for the | |
245 | DAT file, which by default goes to /var/log/lynis-report.dat | |
246 | or /tmp/lynis-report.dat, depending on privileges. | |
247 | Because of that, we will extract the DAT location off | |
248 | lynis' output via parseOutputString(). | |
249 | """ | |
250 | lde = LynisLogDataExtracter(output) | |
268 | 251 | hostname = lde.hostname() |
269 | 252 | ipv4s = lde.ipv4() |
270 | 253 | ipv6s = lde.ipv6() |
334 | 317 | desc=warns[warn] |
335 | 318 | ) |
336 | 319 | |
337 | def processCommandString(self, username, current_path, command_string): | |
338 | """ | |
339 | Lynis does not have a means to specify the location for the | |
340 | DAT file, which by default goes to /var/log/lynis-report.dat | |
341 | or /tmp/lynis-report.dat, depending on privileges. | |
342 | Because of that, we will extract the DAT location off | |
343 | lynis' output via parseOutputString(). | |
344 | """ | |
345 | return | |
346 | ||
347 | def getDatPath(self, output): | |
348 | m = re.search('(\/.+\.dat)$', output, re.MULTILINE) | |
349 | if m: | |
350 | return(m.group(0).strip()) | |
320 | def processOutput(self, command_output): | |
321 | m = re.search('(\/.+\.dat)$', command_output, re.MULTILINE) | |
322 | file_path = m.group(0).strip() | |
323 | self._parse_filename(file_path) | |
351 | 324 | |
352 | 325 | |
353 | 326 | def createPlugin(): |
0 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
1 | <graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd"> | |
2 | <!--Created by yFiles for Java 2.7.0.2--> | |
3 | <key for="graphml" id="d0" yfiles.type="resources"/> | |
4 | <key attr.name="MaltegoEntity" for="node" id="d1"/> | |
5 | <key for="node" id="d2" yfiles.type="nodegraphics"/> | |
6 | <key attr.name="MaltegoLink" for="edge" id="d3"/> | |
7 | <key for="edge" id="d4" yfiles.type="edgegraphics"/> | |
8 | <graph edgedefault="directed" id="G"> | |
9 | <node id="n0"> | |
10 | <data key="d1"> | |
11 | <mtg:MaltegoEntity xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.Domain"> | |
12 | <mtg:Properties displayValue="fqdn" value="fqdn"> | |
13 | <mtg:Property displayName="Domain Name" hidden="false" name="fqdn" nullable="true" readonly="false" type="string"> | |
14 | <mtg:Value>ekoparty.org</mtg:Value> | |
15 | </mtg:Property> | |
16 | <mtg:Property displayName="WHOIS Info" hidden="false" name="whois-info" nullable="true" readonly="false" type="string"> | |
17 | <mtg:Value/> | |
18 | </mtg:Property> | |
19 | </mtg:Properties> | |
20 | <mtg:DisplayInformation/> | |
21 | <mtg:Weight>0</mtg:Weight> | |
22 | </mtg:MaltegoEntity> | |
23 | </data> | |
24 | <data key="d2"> | |
25 | <mtg:EntityRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"> | |
26 | <mtg:Position x="412.79999999999995" y="46.46875"/> | |
27 | </mtg:EntityRenderer> | |
28 | </data> | |
29 | </node> | |
30 | <node id="n1"> | |
31 | <data key="d1"> | |
32 | <mtg:MaltegoEntity xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.MXRecord"> | |
33 | <mtg:Properties displayValue="fqdn" value="fqdn"> | |
34 | <mtg:Property displayName="Priority" hidden="false" name="mxrecord.priority" nullable="true" readonly="false" type="int"> | |
35 | <mtg:Value>0</mtg:Value> | |
36 | </mtg:Property> | |
37 | <mtg:Property displayName="MX Record" hidden="false" name="fqdn" nullable="true" readonly="false" type="string"> | |
38 | <mtg:Value>mail.ekoparty.org</mtg:Value> | |
39 | </mtg:Property> | |
40 | </mtg:Properties> | |
41 | <mtg:DisplayInformation> | |
42 | <mtg:DisplayElement name="Generator detail"><![CDATA[<table><tr><td class=three>Source</td><td class=two>ekoparty.org</td><td class=three>(Domain)</td></tr><tr><td class=three>Transform</td><td class=two colspan=2>To DNS Name - MX (mail server)</td></tr><tr><td class=three>Result</td><td class=two>mail.ekoparty.org</td><td class=three>(MXrecord)</td></tr><tr><td class=three>Gen. date</td><td class=two colspan=2>2011-6-27 23:46</td></tr></table></font>]]></mtg:DisplayElement> | |
43 | </mtg:DisplayInformation> | |
44 | <mtg:Weight>100</mtg:Weight> | |
45 | </mtg:MaltegoEntity> | |
46 | </data> | |
47 | <data key="d2"> | |
48 | <mtg:EntityRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"> | |
49 | <mtg:Position x="546.375" y="445.28125"/> | |
50 | </mtg:EntityRenderer> | |
51 | </data> | |
52 | </node> | |
53 | <node id="n2"> | |
54 | <data key="d1"> | |
55 | <mtg:MaltegoEntity xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.EmailAddress"> | |
56 | <mtg:Properties displayValue="email" value="email"> | |
57 | <mtg:Property displayName="URLs" hidden="false" name="URLS" nullable="true" readonly="false" type="string"> | |
58 | <mtg:Value>http://www.diariodecuyo.com.ar/home/new_noticia.php?noticia_id=420508 Diario de Cuyo - Conferencias internacionales de seguridad | |
59 | http://www.diariodecuyo.com.ar/imagenes/2010/09/EDICION/7menu24.pdf Pagina24y25_Maquetaci��n 1 | |
60 | </mtg:Value> | |
61 | </mtg:Property> | |
62 | <mtg:Property displayName="Email Address" hidden="false" name="email" nullable="true" readonly="false" type="string"> | |
63 | <mtg:Value>[email protected]</mtg:Value> | |
64 | </mtg:Property> | |
65 | </mtg:Properties> | |
66 | <mtg:DisplayInformation> | |
67 | <mtg:DisplayElement name="Generator detail"><![CDATA[<table><tr><td class=three>Source</td><td class=two>ekoparty.org</td><td class=three>(Domain)</td></tr><tr><td class=three>Transform</td><td class=two colspan=2>To Emails @domain [using Search Engine]</td></tr><tr><td class=three>Result</td><td class=two>[email protected]</td><td class=three>(EmailAddress)</td></tr><tr><td class=three>Gen. date</td><td class=two colspan=2>2011-6-27 23:49</td></tr></table></font>]]></mtg:DisplayElement> | |
68 | <mtg:DisplayElement name="Snippet(s):"><![CDATA[<table><tr><td class=one>Diario de Cuyo - Conferencias internacionales de seguridad</td><td class=three>Indexed on:2011/1/21</td></tr><tr><td class=one colspan=2><a href="http://www.diariodecuyo.com.ar/home/new_noticia.php?noticia_id=420508">[www.diariodecuyo.com.ar]</a></td></tr><tr><td class=two colspan=2>Son 23 las conferencias confirmadas para los d��as 16 y 17 de ' Sitio oficial: www.ekoparty.org. Contacto: [email protected] - (11) 6841 1010. Otras '</td></tr></table><br><table><tr><td class=one>Pagina24y25_Maquetaci��n 1</td><td class=three>Indexed on:2010/9/7</td></tr><tr><td class=one colspan=2><a href="http://www.diariodecuyo.com.ar/imagenes/2010/09/EDICION/7menu24.pdf">[www.diariodecuyo.com.ar]</a></td></tr><tr><td class=two colspan=2>M��s que la p��rdida material, lo. duro para cualquier usuario es el ' www.ekoparty.org - Contacto: [email protected] (11) 6841 1010. net. Una vez que el '</td></tr></table><br>]]></mtg:DisplayElement> | |
69 | </mtg:DisplayInformation> | |
70 | <mtg:Weight>200</mtg:Weight> | |
71 | </mtg:MaltegoEntity> | |
72 | </data> | |
73 | <data key="d2"> | |
74 | <mtg:EntityRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"> | |
75 | <mtg:Position x="250.0" y="194.40625"/> | |
76 | </mtg:EntityRenderer> | |
77 | </data> | |
78 | </node> | |
79 | <node id="n3"> | |
80 | <data key="d1"> | |
81 | <mtg:MaltegoEntity xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.EmailAddress"> | |
82 | <mtg:Properties displayValue="email" value="email"> | |
83 | <mtg:Property displayName="URLs" hidden="false" name="URLS" nullable="true" readonly="false" type="string"> | |
84 | <mtg:Value>http://zh-hk.facebook.com/pages/ekoparty-security-conference/16162244291 ekoparty security conference | Facebook | |
85 | http://www.tecnozona.com/zona_del_que_diran/ekoparty-ya-esta-en-marcha/ Ekoparty ya est�� en marcha | |
86 | </mtg:Value> | |
87 | </mtg:Property> | |
88 | <mtg:Property displayName="Email Address" hidden="false" name="email" nullable="true" readonly="false" type="string"> | |
89 | <mtg:Value>[email protected]</mtg:Value> | |
90 | </mtg:Property> | |
91 | </mtg:Properties> | |
92 | <mtg:DisplayInformation> | |
93 | <mtg:DisplayElement name="Generator detail"><![CDATA[<table><tr><td class=three>Source</td><td class=two>ekoparty.org</td><td class=three>(Domain)</td></tr><tr><td class=three>Transform</td><td class=two colspan=2>To Emails @domain [using Search Engine]</td></tr><tr><td class=three>Result</td><td class=two>[email protected]</td><td class=three>(EmailAddress)</td></tr><tr><td class=three>Gen. date</td><td class=two colspan=2>2011-6-27 23:49</td></tr></table></font>]]></mtg:DisplayElement> | |
94 | <mtg:DisplayElement name="Snippet(s):"><![CDATA[<table><tr><td class=one>ekoparty security conference | Facebook</td><td class=three>Indexed on:2010/7/15</td></tr><tr><td class=one colspan=2><a href="http://zh-hk.facebook.com/pages/ekoparty-security-conference/16162244291">[zh-hk.facebook.com]</a></td></tr><tr><td class=two colspan=2>������������Facebook������ ekoparty security conference ���������������������������Facebook��� ekoparty security conference ��������� ' Env��a tu slogans a [email protected]. Una vez recibidos y ordenados '</td></tr></table><br><table><tr><td class=one>Ekoparty ya est�� en marcha</td><td class=three>Indexed on:2011/6/9</td></tr><tr><td class=one colspan=2><a href="http://www.tecnozona.com/zona_del_que_diran/ekoparty-ya-esta-en-marcha/">[www.tecnozona.com]</a></td></tr><tr><td class=two colspan=2>Ten��s hasta el 26 de mayo para enviar tus slogans (hasta 3) a [email protected]. Una vez recibidos y ordenados, se va a hacer online una votaci��n '</td></tr></table><br>]]></mtg:DisplayElement> | |
95 | </mtg:DisplayInformation> | |
96 | <mtg:Weight>200</mtg:Weight> | |
97 | </mtg:MaltegoEntity> | |
98 | </data> | |
99 | <data key="d2"> | |
100 | <mtg:EntityRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"> | |
101 | <mtg:Position x="86.5" y="194.40625"/> | |
102 | </mtg:EntityRenderer> | |
103 | </data> | |
104 | </node> | |
105 | <node id="n4"> | |
106 | <data key="d1"> | |
107 | <mtg:MaltegoEntity xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.EmailAddress"> | |
108 | <mtg:Properties displayValue="email" value="email"> | |
109 | <mtg:Property displayName="URLs" hidden="false" name="URLS" nullable="true" readonly="false" type="string"> | |
110 | <mtg:Value>http://twitter.com/ekoparty/status/23111351685 Twitter / ekoparty: Just for fun! Resolve this ' | |
111 | http://zh-tw.facebook.com/pages/ekoparty-security-conference/16162244291?_fb_noscript=1 ekoparty security conference | Facebook | |
112 | http://www.madrimasd.org/iberoamerica/actividades/mostrar_info.asp?id=45272 Actividades | |
113 | http://www.canal-ar.com.ar/Sosnoticia/sosnoticiamuestra.asp?Id=1955 CanalAR - Core Security renueva su presencia en ekoparty ' | |
114 | </mtg:Value> | |
115 | </mtg:Property> | |
116 | <mtg:Property displayName="Email Address" hidden="false" name="email" nullable="true" readonly="false" type="string"> | |
117 | <mtg:Value>[email protected]</mtg:Value> | |
118 | </mtg:Property> | |
119 | </mtg:Properties> | |
120 | <mtg:DisplayInformation> | |
121 | <mtg:DisplayElement name="Generator detail"><![CDATA[<table><tr><td class=three>Source</td><td class=two>ekoparty.org</td><td class=three>(Domain)</td></tr><tr><td class=three>Transform</td><td class=two colspan=2>To Emails @domain [using Search Engine]</td></tr><tr><td class=three>Result</td><td class=two>[email protected]</td><td class=three>(EmailAddress)</td></tr><tr><td class=three>Gen. date</td><td class=two colspan=2>2011-6-27 23:49</td></tr></table></font>]]></mtg:DisplayElement> | |
122 | <mtg:DisplayElement name="Snippet(s):"><![CDATA[<table><tr><td class=one>Twitter / ekoparty: Just for fun! Resolve this '</td><td class=three>Indexed on:2010/9/6</td></tr><tr><td class=one colspan=2><a href="http://twitter.com/ekoparty/status/23111351685">[twitter.com]</a></td></tr><tr><td class=two colspan=2>Just for fun! Resolve this challenge and get a 15% off Send the token to organizacion @ ekoparty.org | DM http://bit.ly/avQpcx</td></tr></table><br><table><tr><td class=one>CanalAR - Core Security renueva su presencia en ekoparty '</td><td class=three>Indexed on:2011/6/3</td></tr><tr><td class=one colspan=2><a href="http://www.canal-ar.com.ar/Sosnoticia/sosnoticiamuestra.asp?Id=1955">[www.canal-ar.com.ar]</a></td></tr><tr><td class=two colspan=2>Periodismo y An��lisis en el mundo argentino de las tecnolog��as de la informaci��n ' en http://www.ekoparty.org/, escribir a [email protected] o comunicarse al (+54 '</td></tr></table><br><table><tr><td class=one>Actividades</td><td class=three>Indexed on:2011/6/10</td></tr><tr><td class=one colspan=2><a href="http://www.madrimasd.org/iberoamerica/actividades/mostrar_info.asp?id=45272">[www.madrimasd.org]</a></td></tr><tr><td class=two colspan=2>E-mail: [email protected]. Resumen: Asistentes, invitados, especialistas y referentes de todo el mundo tienen la oportunidad de involucrarse '</td></tr></table><br><table><tr><td class=one>ekoparty security conference | Facebook</td><td class=three>Indexed on:2011/5/26</td></tr><tr><td class=one colspan=2><a href="http://zh-tw.facebook.com/pages/ekoparty-security-conference/16162244291?_fb_noscript=1">[zh-tw.facebook.com]</a></td></tr><tr><td class=two colspan=2>ekoparty security conference - A security conference hosted yearly in Buenos Aires | Facebook ' un email a organizacion @ ekoparty.org te enviaremos una carpeta con '</td></tr></table><br>]]></mtg:DisplayElement> | |
123 | </mtg:DisplayInformation> | |
124 | <mtg:Weight>28</mtg:Weight> | |
125 | </mtg:MaltegoEntity> | |
126 | </data> | |
127 | <data key="d2"> | |
128 | <mtg:EntityRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"> | |
129 | <mtg:Position x="714.875" y="445.28125"/> | |
130 | </mtg:EntityRenderer> | |
131 | </data> | |
132 | </node> | |
133 | <node id="n5"> | |
134 | <data key="d1"> | |
135 | <mtg:MaltegoEntity xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.EmailAddress"> | |
136 | <mtg:Properties displayValue="email" value="email"> | |
137 | <mtg:Property displayName="URLs" hidden="false" name="URLS" nullable="true" readonly="false" type="string"> | |
138 | <mtg:Value>http://cfp.ekoparty.org/ ekoparty - CALL FOR PAPERS | |
139 | </mtg:Value> | |
140 | </mtg:Property> | |
141 | <mtg:Property displayName="Email Address" hidden="false" name="email" nullable="true" readonly="false" type="string"> | |
142 | <mtg:Value>[email protected]</mtg:Value> | |
143 | </mtg:Property> | |
144 | </mtg:Properties> | |
145 | <mtg:DisplayInformation> | |
146 | <mtg:DisplayElement name="Generator detail"><![CDATA[<table><tr><td class=three>Source</td><td class=two>ekoparty.org</td><td class=three>(Domain)</td></tr><tr><td class=three>Transform</td><td class=two colspan=2>To Emails @domain [using Search Engine]</td></tr><tr><td class=three>Result</td><td class=two>[email protected]</td><td class=three>(EmailAddress)</td></tr><tr><td class=three>Gen. date</td><td class=two colspan=2>2011-6-27 23:49</td></tr></table></font>]]></mtg:DisplayElement> | |
147 | <mtg:DisplayElement name="Snippet(s):"><![CDATA[<table><tr><td class=one>ekoparty - CALL FOR PAPERS</td><td class=three>Indexed on:2011/5/30</td></tr><tr><td class=one colspan=2><a href="http://cfp.ekoparty.org/">[cfp.ekoparty.org]</a></td></tr><tr><td class=two colspan=2>If you don't have an account, please Signup to get started. For training submissions, or questions about our CFP process, contact us directly at [email protected] '</td></tr></table><br>]]></mtg:DisplayElement> | |
148 | </mtg:DisplayInformation> | |
149 | <mtg:Weight>0</mtg:Weight> | |
150 | </mtg:MaltegoEntity> | |
151 | </data> | |
152 | <data key="d2"> | |
153 | <mtg:EntityRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"> | |
154 | <mtg:Position x="86.5" y="297.34375"/> | |
155 | </mtg:EntityRenderer> | |
156 | </data> | |
157 | </node> | |
158 | <node id="n6"> | |
159 | <data key="d1"> | |
160 | <mtg:MaltegoEntity xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.EmailAddress"> | |
161 | <mtg:Properties displayValue="email" value="email"> | |
162 | <mtg:Property displayName="URLs" hidden="false" name="URLS" nullable="true" readonly="false" type="string"> | |
163 | <mtg:Value>http://www.pay2pay.com.ar/clientes/ekoparty-security-conference/register-eng.php ekoparty Security Conference | |
164 | </mtg:Value> | |
165 | </mtg:Property> | |
166 | <mtg:Property displayName="Email Address" hidden="false" name="email" nullable="true" readonly="false" type="string"> | |
167 | <mtg:Value>[email protected]</mtg:Value> | |
168 | </mtg:Property> | |
169 | </mtg:Properties> | |
170 | <mtg:DisplayInformation> | |
171 | <mtg:DisplayElement name="Generator detail"><![CDATA[<table><tr><td class=three>Source</td><td class=two>ekoparty.org</td><td class=three>(Domain)</td></tr><tr><td class=three>Transform</td><td class=two colspan=2>To Emails @domain [using Search Engine]</td></tr><tr><td class=three>Result</td><td class=two>[email protected]</td><td class=three>(EmailAddress)</td></tr><tr><td class=three>Gen. date</td><td class=two colspan=2>2011-6-27 23:49</td></tr></table></font>]]></mtg:DisplayElement> | |
172 | <mtg:DisplayElement name="Snippet(s):"><![CDATA[<table><tr><td class=one>ekoparty Security Conference</td><td class=three>Indexed on:2011/6/17</td></tr><tr><td class=one colspan=2><a href="http://www.pay2pay.com.ar/clientes/ekoparty-security-conference/register-eng.php">[www.pay2pay.com.ar]</a></td></tr><tr><td class=two colspan=2>For press or coporate groups acreditations please send a mail to: organizacion ' sponsor at the seventh edition of ekoparty contact us at: [email protected] '</td></tr></table><br>]]></mtg:DisplayElement> | |
173 | </mtg:DisplayInformation> | |
174 | <mtg:Weight>0</mtg:Weight> | |
175 | </mtg:MaltegoEntity> | |
176 | </data> | |
177 | <data key="d2"> | |
178 | <mtg:EntityRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"> | |
179 | <mtg:Position x="425.25" y="179.40625"/> | |
180 | </mtg:EntityRenderer> | |
181 | </data> | |
182 | </node> | |
183 | <node id="n7"> | |
184 | <data key="d1"> | |
185 | <mtg:MaltegoEntity xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.DNSName"> | |
186 | <mtg:Properties displayValue="fqdn" value="fqdn"> | |
187 | <mtg:Property displayName="DNS Name" hidden="false" name="fqdn" nullable="true" readonly="false" type="string"> | |
188 | <mtg:Value>mail.ekoparty.org</mtg:Value> | |
189 | </mtg:Property> | |
190 | </mtg:Properties> | |
191 | <mtg:DisplayInformation> | |
192 | <mtg:DisplayElement name="Generator detail"><![CDATA[<table><tr><td class=three>Source</td><td class=two>ekoparty.org</td><td class=three>(Domain)</td></tr><tr><td class=three>Transform</td><td class=two colspan=2>To DNS Name [Find common DNS names]</td></tr><tr><td class=three>Result</td><td class=two>mail.ekoparty.org</td><td class=three>(DNSName)</td></tr><tr><td class=three>Gen. date</td><td class=two colspan=2>2011-6-27 23:50</td></tr></table></font>]]></mtg:DisplayElement> | |
193 | </mtg:DisplayInformation> | |
194 | <mtg:Weight>100</mtg:Weight> | |
195 | </mtg:MaltegoEntity> | |
196 | </data> | |
197 | <data key="d2"> | |
198 | <mtg:EntityRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"> | |
199 | <mtg:Position x="622.75" y="245.875"/> | |
200 | </mtg:EntityRenderer> | |
201 | </data> | |
202 | </node> | |
203 | <node id="n8"> | |
204 | <data key="d1"> | |
205 | <mtg:MaltegoEntity xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.DNSName"> | |
206 | <mtg:Properties displayValue="fqdn" value="fqdn"> | |
207 | <mtg:Property displayName="DNS Name" hidden="false" name="fqdn" nullable="true" readonly="false" type="string"> | |
208 | <mtg:Value>blog.ekoparty.org</mtg:Value> | |
209 | </mtg:Property> | |
210 | </mtg:Properties> | |
211 | <mtg:DisplayInformation> | |
212 | <mtg:DisplayElement name="Generator detail"><![CDATA[<table><tr><td class=three>Source</td><td class=two>ekoparty.org</td><td class=three>(Domain)</td></tr><tr><td class=three>Transform</td><td class=two colspan=2>To DNS Name [Find common DNS names]</td></tr><tr><td class=three>Result</td><td class=two>blog.ekoparty.org</td><td class=three>(DNSName)</td></tr><tr><td class=three>Gen. date</td><td class=two colspan=2>2011-6-27 23:50</td></tr></table></font>]]></mtg:DisplayElement> | |
213 | </mtg:DisplayInformation> | |
214 | <mtg:Weight>100</mtg:Weight> | |
215 | </mtg:MaltegoEntity> | |
216 | </data> | |
217 | <data key="d2"> | |
218 | <mtg:EntityRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"> | |
219 | <mtg:Position x="250.0" y="297.34375"/> | |
220 | </mtg:EntityRenderer> | |
221 | </data> | |
222 | </node> | |
223 | <edge id="e0" source="n0" target="n1"> | |
224 | <data key="d3"> | |
225 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.transform-link"> | |
226 | <mtg:Properties> | |
227 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
228 | <mtg:Value>0</mtg:Value> | |
229 | </mtg:Property> | |
230 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
231 | <mtg:Value>0</mtg:Value> | |
232 | </mtg:Property> | |
233 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
234 | <mtg:Value>1</mtg:Value> | |
235 | </mtg:Property> | |
236 | <mtg:Property displayName="Date run" hidden="false" name="maltego.link.transform.run-date" nullable="true" readonly="true" type="date"> | |
237 | <mtg:Value>2011-06-27 19:46:28.176 EDT</mtg:Value> | |
238 | </mtg:Property> | |
239 | <mtg:Property displayName="Transform name" hidden="false" name="maltego.link.transform.display-name" nullable="true" readonly="true" type="string"> | |
240 | <mtg:Value>To DNS Name - MX (mail server)</mtg:Value> | |
241 | </mtg:Property> | |
242 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
243 | <mtg:Value>0</mtg:Value> | |
244 | </mtg:Property> | |
245 | <mtg:Property displayName="Transform" hidden="true" name="maltego.link.transform.name" nullable="true" readonly="true" type="string"> | |
246 | <mtg:Value>paterva.v2.DomainToMXrecord_DNS</mtg:Value> | |
247 | </mtg:Property> | |
248 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
249 | <mtg:Value>-4144960</mtg:Value> | |
250 | </mtg:Property> | |
251 | <mtg:Property displayName="Transform version" hidden="false" name="maltego.link.transform.version" nullable="true" readonly="true" type="string"> | |
252 | <mtg:Value>1.0.0</mtg:Value> | |
253 | </mtg:Property> | |
254 | </mtg:Properties> | |
255 | </mtg:MaltegoLink> | |
256 | </data> | |
257 | <data key="d4"> | |
258 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
259 | </data> | |
260 | </edge> | |
261 | <edge id="e1" source="n0" target="n2"> | |
262 | <data key="d3"> | |
263 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.transform-link"> | |
264 | <mtg:Properties> | |
265 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
266 | <mtg:Value>0</mtg:Value> | |
267 | </mtg:Property> | |
268 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
269 | <mtg:Value>0</mtg:Value> | |
270 | </mtg:Property> | |
271 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
272 | <mtg:Value>1</mtg:Value> | |
273 | </mtg:Property> | |
274 | <mtg:Property displayName="Date run" hidden="false" name="maltego.link.transform.run-date" nullable="true" readonly="true" type="date"> | |
275 | <mtg:Value>2011-06-27 19:49:20.365 EDT</mtg:Value> | |
276 | </mtg:Property> | |
277 | <mtg:Property displayName="Transform name" hidden="false" name="maltego.link.transform.display-name" nullable="true" readonly="true" type="string"> | |
278 | <mtg:Value>To Emails @domain [using Search Engine]</mtg:Value> | |
279 | </mtg:Property> | |
280 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
281 | <mtg:Value>0</mtg:Value> | |
282 | </mtg:Property> | |
283 | <mtg:Property displayName="Transform" hidden="true" name="maltego.link.transform.name" nullable="true" readonly="true" type="string"> | |
284 | <mtg:Value>paterva.v2.DomainToEmailAddress_AtDomain_SE</mtg:Value> | |
285 | </mtg:Property> | |
286 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
287 | <mtg:Value>-4144960</mtg:Value> | |
288 | </mtg:Property> | |
289 | <mtg:Property displayName="Transform version" hidden="false" name="maltego.link.transform.version" nullable="true" readonly="true" type="string"> | |
290 | <mtg:Value>1.0.0</mtg:Value> | |
291 | </mtg:Property> | |
292 | </mtg:Properties> | |
293 | </mtg:MaltegoLink> | |
294 | </data> | |
295 | <data key="d4"> | |
296 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
297 | </data> | |
298 | </edge> | |
299 | <edge id="e2" source="n0" target="n3"> | |
300 | <data key="d3"> | |
301 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.transform-link"> | |
302 | <mtg:Properties> | |
303 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
304 | <mtg:Value>0</mtg:Value> | |
305 | </mtg:Property> | |
306 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
307 | <mtg:Value>0</mtg:Value> | |
308 | </mtg:Property> | |
309 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
310 | <mtg:Value>1</mtg:Value> | |
311 | </mtg:Property> | |
312 | <mtg:Property displayName="Date run" hidden="false" name="maltego.link.transform.run-date" nullable="true" readonly="true" type="date"> | |
313 | <mtg:Value>2011-06-27 19:49:20.365 EDT</mtg:Value> | |
314 | </mtg:Property> | |
315 | <mtg:Property displayName="Transform name" hidden="false" name="maltego.link.transform.display-name" nullable="true" readonly="true" type="string"> | |
316 | <mtg:Value>To Emails @domain [using Search Engine]</mtg:Value> | |
317 | </mtg:Property> | |
318 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
319 | <mtg:Value>0</mtg:Value> | |
320 | </mtg:Property> | |
321 | <mtg:Property displayName="Transform" hidden="true" name="maltego.link.transform.name" nullable="true" readonly="true" type="string"> | |
322 | <mtg:Value>paterva.v2.DomainToEmailAddress_AtDomain_SE</mtg:Value> | |
323 | </mtg:Property> | |
324 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
325 | <mtg:Value>-4144960</mtg:Value> | |
326 | </mtg:Property> | |
327 | <mtg:Property displayName="Transform version" hidden="false" name="maltego.link.transform.version" nullable="true" readonly="true" type="string"> | |
328 | <mtg:Value>1.0.0</mtg:Value> | |
329 | </mtg:Property> | |
330 | </mtg:Properties> | |
331 | </mtg:MaltegoLink> | |
332 | </data> | |
333 | <data key="d4"> | |
334 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
335 | </data> | |
336 | </edge> | |
337 | <edge id="e3" source="n0" target="n4"> | |
338 | <data key="d3"> | |
339 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.transform-link"> | |
340 | <mtg:Properties> | |
341 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
342 | <mtg:Value>0</mtg:Value> | |
343 | </mtg:Property> | |
344 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
345 | <mtg:Value>0</mtg:Value> | |
346 | </mtg:Property> | |
347 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
348 | <mtg:Value>1</mtg:Value> | |
349 | </mtg:Property> | |
350 | <mtg:Property displayName="Date run" hidden="false" name="maltego.link.transform.run-date" nullable="true" readonly="true" type="date"> | |
351 | <mtg:Value>2011-06-27 19:49:20.365 EDT</mtg:Value> | |
352 | </mtg:Property> | |
353 | <mtg:Property displayName="Transform name" hidden="false" name="maltego.link.transform.display-name" nullable="true" readonly="true" type="string"> | |
354 | <mtg:Value>To Emails @domain [using Search Engine]</mtg:Value> | |
355 | </mtg:Property> | |
356 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
357 | <mtg:Value>0</mtg:Value> | |
358 | </mtg:Property> | |
359 | <mtg:Property displayName="Transform" hidden="true" name="maltego.link.transform.name" nullable="true" readonly="true" type="string"> | |
360 | <mtg:Value>paterva.v2.DomainToEmailAddress_AtDomain_SE</mtg:Value> | |
361 | </mtg:Property> | |
362 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
363 | <mtg:Value>-4144960</mtg:Value> | |
364 | </mtg:Property> | |
365 | <mtg:Property displayName="Transform version" hidden="false" name="maltego.link.transform.version" nullable="true" readonly="true" type="string"> | |
366 | <mtg:Value>1.0.0</mtg:Value> | |
367 | </mtg:Property> | |
368 | </mtg:Properties> | |
369 | </mtg:MaltegoLink> | |
370 | </data> | |
371 | <data key="d4"> | |
372 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
373 | </data> | |
374 | </edge> | |
375 | <edge id="e4" source="n0" target="n5"> | |
376 | <data key="d3"> | |
377 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.transform-link"> | |
378 | <mtg:Properties> | |
379 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
380 | <mtg:Value>0</mtg:Value> | |
381 | </mtg:Property> | |
382 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
383 | <mtg:Value>0</mtg:Value> | |
384 | </mtg:Property> | |
385 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
386 | <mtg:Value>1</mtg:Value> | |
387 | </mtg:Property> | |
388 | <mtg:Property displayName="Date run" hidden="false" name="maltego.link.transform.run-date" nullable="true" readonly="true" type="date"> | |
389 | <mtg:Value>2011-06-27 19:49:20.365 EDT</mtg:Value> | |
390 | </mtg:Property> | |
391 | <mtg:Property displayName="Transform name" hidden="false" name="maltego.link.transform.display-name" nullable="true" readonly="true" type="string"> | |
392 | <mtg:Value>To Emails @domain [using Search Engine]</mtg:Value> | |
393 | </mtg:Property> | |
394 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
395 | <mtg:Value>0</mtg:Value> | |
396 | </mtg:Property> | |
397 | <mtg:Property displayName="Transform" hidden="true" name="maltego.link.transform.name" nullable="true" readonly="true" type="string"> | |
398 | <mtg:Value>paterva.v2.DomainToEmailAddress_AtDomain_SE</mtg:Value> | |
399 | </mtg:Property> | |
400 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
401 | <mtg:Value>-4144960</mtg:Value> | |
402 | </mtg:Property> | |
403 | <mtg:Property displayName="Transform version" hidden="false" name="maltego.link.transform.version" nullable="true" readonly="true" type="string"> | |
404 | <mtg:Value>1.0.0</mtg:Value> | |
405 | </mtg:Property> | |
406 | </mtg:Properties> | |
407 | </mtg:MaltegoLink> | |
408 | </data> | |
409 | <data key="d4"> | |
410 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
411 | </data> | |
412 | </edge> | |
413 | <edge id="e5" source="n0" target="n6"> | |
414 | <data key="d3"> | |
415 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.transform-link"> | |
416 | <mtg:Properties> | |
417 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
418 | <mtg:Value>0</mtg:Value> | |
419 | </mtg:Property> | |
420 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
421 | <mtg:Value>0</mtg:Value> | |
422 | </mtg:Property> | |
423 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
424 | <mtg:Value>1</mtg:Value> | |
425 | </mtg:Property> | |
426 | <mtg:Property displayName="Date run" hidden="false" name="maltego.link.transform.run-date" nullable="true" readonly="true" type="date"> | |
427 | <mtg:Value>2011-06-27 19:49:20.365 EDT</mtg:Value> | |
428 | </mtg:Property> | |
429 | <mtg:Property displayName="Transform name" hidden="false" name="maltego.link.transform.display-name" nullable="true" readonly="true" type="string"> | |
430 | <mtg:Value>To Emails @domain [using Search Engine]</mtg:Value> | |
431 | </mtg:Property> | |
432 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
433 | <mtg:Value>0</mtg:Value> | |
434 | </mtg:Property> | |
435 | <mtg:Property displayName="Transform" hidden="true" name="maltego.link.transform.name" nullable="true" readonly="true" type="string"> | |
436 | <mtg:Value>paterva.v2.DomainToEmailAddress_AtDomain_SE</mtg:Value> | |
437 | </mtg:Property> | |
438 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
439 | <mtg:Value>-4144960</mtg:Value> | |
440 | </mtg:Property> | |
441 | <mtg:Property displayName="Transform version" hidden="false" name="maltego.link.transform.version" nullable="true" readonly="true" type="string"> | |
442 | <mtg:Value>1.0.0</mtg:Value> | |
443 | </mtg:Property> | |
444 | </mtg:Properties> | |
445 | </mtg:MaltegoLink> | |
446 | </data> | |
447 | <data key="d4"> | |
448 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
449 | </data> | |
450 | </edge> | |
451 | <edge id="e6" source="n0" target="n8"> | |
452 | <data key="d3"> | |
453 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.transform-link"> | |
454 | <mtg:Properties> | |
455 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
456 | <mtg:Value>0</mtg:Value> | |
457 | </mtg:Property> | |
458 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
459 | <mtg:Value>0</mtg:Value> | |
460 | </mtg:Property> | |
461 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
462 | <mtg:Value>1</mtg:Value> | |
463 | </mtg:Property> | |
464 | <mtg:Property displayName="Date run" hidden="false" name="maltego.link.transform.run-date" nullable="true" readonly="true" type="date"> | |
465 | <mtg:Value>2011-06-27 19:50:35.637 EDT</mtg:Value> | |
466 | </mtg:Property> | |
467 | <mtg:Property displayName="Transform name" hidden="false" name="maltego.link.transform.display-name" nullable="true" readonly="true" type="string"> | |
468 | <mtg:Value>To DNS Name [Find common DNS names]</mtg:Value> | |
469 | </mtg:Property> | |
470 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
471 | <mtg:Value>0</mtg:Value> | |
472 | </mtg:Property> | |
473 | <mtg:Property displayName="Transform" hidden="true" name="maltego.link.transform.name" nullable="true" readonly="true" type="string"> | |
474 | <mtg:Value>paterva.v2.DomainToDNSName_DNSBrute</mtg:Value> | |
475 | </mtg:Property> | |
476 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
477 | <mtg:Value>-4144960</mtg:Value> | |
478 | </mtg:Property> | |
479 | <mtg:Property displayName="Transform version" hidden="false" name="maltego.link.transform.version" nullable="true" readonly="true" type="string"> | |
480 | <mtg:Value>1.0.0</mtg:Value> | |
481 | </mtg:Property> | |
482 | </mtg:Properties> | |
483 | </mtg:MaltegoLink> | |
484 | </data> | |
485 | <data key="d4"> | |
486 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
487 | </data> | |
488 | </edge> | |
489 | <edge id="e7" source="n0" target="n7"> | |
490 | <data key="d3"> | |
491 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.transform-link"> | |
492 | <mtg:Properties> | |
493 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
494 | <mtg:Value>0</mtg:Value> | |
495 | </mtg:Property> | |
496 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
497 | <mtg:Value>0</mtg:Value> | |
498 | </mtg:Property> | |
499 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
500 | <mtg:Value>1</mtg:Value> | |
501 | </mtg:Property> | |
502 | <mtg:Property displayName="Date run" hidden="false" name="maltego.link.transform.run-date" nullable="true" readonly="true" type="date"> | |
503 | <mtg:Value>2011-06-27 19:50:35.636 EDT</mtg:Value> | |
504 | </mtg:Property> | |
505 | <mtg:Property displayName="Transform name" hidden="false" name="maltego.link.transform.display-name" nullable="true" readonly="true" type="string"> | |
506 | <mtg:Value>To DNS Name [Find common DNS names]</mtg:Value> | |
507 | </mtg:Property> | |
508 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
509 | <mtg:Value>0</mtg:Value> | |
510 | </mtg:Property> | |
511 | <mtg:Property displayName="Transform" hidden="true" name="maltego.link.transform.name" nullable="true" readonly="true" type="string"> | |
512 | <mtg:Value>paterva.v2.DomainToDNSName_DNSBrute</mtg:Value> | |
513 | </mtg:Property> | |
514 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
515 | <mtg:Value>-4144960</mtg:Value> | |
516 | </mtg:Property> | |
517 | <mtg:Property displayName="Transform version" hidden="false" name="maltego.link.transform.version" nullable="true" readonly="true" type="string"> | |
518 | <mtg:Value>1.0.0</mtg:Value> | |
519 | </mtg:Property> | |
520 | </mtg:Properties> | |
521 | </mtg:MaltegoLink> | |
522 | </data> | |
523 | <data key="d4"> | |
524 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
525 | </data> | |
526 | </edge> | |
527 | <edge id="e8" source="n7" target="n1"> | |
528 | <data key="d3"> | |
529 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.manual-link"> | |
530 | <mtg:Properties> | |
531 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
532 | <mtg:Value>0</mtg:Value> | |
533 | </mtg:Property> | |
534 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
535 | <mtg:Value>0</mtg:Value> | |
536 | </mtg:Property> | |
537 | <mtg:Property displayName="Reference" hidden="false" name="maltego.link.manual.reference" nullable="true" readonly="false" type="string"> | |
538 | <mtg:Value/> | |
539 | </mtg:Property> | |
540 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
541 | <mtg:Value>1</mtg:Value> | |
542 | </mtg:Property> | |
543 | <mtg:Property displayName="Label" hidden="false" name="maltego.link.manual.type" nullable="true" readonly="false" type="string"> | |
544 | <mtg:Value/> | |
545 | </mtg:Property> | |
546 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
547 | <mtg:Value>0</mtg:Value> | |
548 | </mtg:Property> | |
549 | <mtg:Property displayName="Description" hidden="false" name="maltego.link.manual.description" nullable="true" readonly="false" type="string"> | |
550 | <mtg:Value/> | |
551 | </mtg:Property> | |
552 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
553 | <mtg:Value>-6895466</mtg:Value> | |
554 | </mtg:Property> | |
555 | </mtg:Properties> | |
556 | </mtg:MaltegoLink> | |
557 | </data> | |
558 | <data key="d4"> | |
559 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
560 | </data> | |
561 | </edge> | |
562 | <edge id="e9" source="n7" target="n4"> | |
563 | <data key="d3"> | |
564 | <mtg:MaltegoLink xmlns:mtg="http://maltego.paterva.com/xml/mtgx" type="maltego.link.manual-link"> | |
565 | <mtg:Properties> | |
566 | <mtg:Property displayName="Weight" hidden="false" name="maltego.link.weight" nullable="true" readonly="false" type="int"> | |
567 | <mtg:Value>0</mtg:Value> | |
568 | </mtg:Property> | |
569 | <mtg:Property displayName="Show Label" hidden="false" name="maltego.link.show-label" nullable="true" readonly="false" type="int"> | |
570 | <mtg:Value>0</mtg:Value> | |
571 | </mtg:Property> | |
572 | <mtg:Property displayName="Reference" hidden="false" name="maltego.link.manual.reference" nullable="true" readonly="false" type="string"> | |
573 | <mtg:Value/> | |
574 | </mtg:Property> | |
575 | <mtg:Property displayName="Thickness" hidden="false" name="maltego.link.thickness" nullable="true" readonly="false" type="int"> | |
576 | <mtg:Value>1</mtg:Value> | |
577 | </mtg:Property> | |
578 | <mtg:Property displayName="Label" hidden="false" name="maltego.link.manual.type" nullable="true" readonly="false" type="string"> | |
579 | <mtg:Value/> | |
580 | </mtg:Property> | |
581 | <mtg:Property displayName="Style" hidden="false" name="maltego.link.style" nullable="true" readonly="false" type="int"> | |
582 | <mtg:Value>0</mtg:Value> | |
583 | </mtg:Property> | |
584 | <mtg:Property displayName="Description" hidden="false" name="maltego.link.manual.description" nullable="true" readonly="false" type="string"> | |
585 | <mtg:Value/> | |
586 | </mtg:Property> | |
587 | <mtg:Property displayName="Color" hidden="false" name="maltego.link.color" nullable="true" readonly="false" type="color"> | |
588 | <mtg:Value>-6895466</mtg:Value> | |
589 | </mtg:Property> | |
590 | </mtg:Properties> | |
591 | </mtg:MaltegoLink> | |
592 | </data> | |
593 | <data key="d4"> | |
594 | <mtg:LinkRenderer xmlns:mtg="http://maltego.paterva.com/xml/mtgx"/> | |
595 | </data> | |
596 | </edge> | |
597 | </graph> | |
598 | <data key="d0"> | |
599 | <y:Resources/> | |
600 | </data> | |
601 | </graphml> |
2 | 2 | Copyright (C) 2015 Infobyte LLC (http://www.infobytesec.com/) |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
6 | ||
7 | import zipfile | |
5 | from faraday_plugins.plugins.plugin import PluginZipFormat | |
8 | 6 | import re |
9 | 7 | import os |
8 | import zipfile | |
10 | 9 | |
11 | 10 | try: |
12 | 11 | import xml.etree.cElementTree as ET |
29 | 28 | __status__ = "Development" |
30 | 29 | |
31 | 30 | |
32 | def openMtgx(mtgx_file): | |
33 | ||
31 | def readMtgx(mtgl_file): | |
32 | maltego_file_graph = "Graphs/Graph1.graphml" | |
33 | xml_graph = ET.parse(mtgl_file.open(maltego_file_graph)) | |
34 | mtgl_file.close() | |
35 | return xml_graph | |
36 | ||
37 | ||
38 | def readMtgl(mtgl_file): | |
34 | 39 | try: |
35 | file = zipfile.ZipFile(mtgx_file, "r") | |
36 | xml = ET.parse(file.open('Graphs/Graph1.graphml')) | |
37 | ||
38 | except: | |
39 | print("Bad report format") | |
40 | maltego_file_company = "Entities/maltego.Company.entity" | |
41 | maltego_file_dns = "Entities/maltego.DNSName.entity" | |
42 | maltego_file_domain = "Entities/maltego.Domain.entity" | |
43 | maltego_file_email = "Entities/maltego.EmailAddress.entity" | |
44 | maltego_file_ipv4 = "Entities/maltego.IPv4Address.entity" | |
45 | maltego_file_location = "Entities/maltego.Location.entity" | |
46 | maltego_file_mxrecord = "Entities/maltego.MXRecord.entity" | |
47 | maltego_file_nsrecord = "Entities/maltego.NSRecord.entity" | |
48 | maltego_file_organization = "Entities/maltego.Organization.entity" | |
49 | maltego_file_person = "Entities/maltego.Person.entity" | |
50 | maltego_file_number = "Entities/maltego.PhoneNumber.entity" | |
51 | maltego_file_website = "Entities/maltego.Website.entity" | |
52 | check_files = {} | |
53 | ||
54 | if maltego_file_company in mtgl_file.namelist(): | |
55 | xml_company = ET.parse(mtgl_file.open(maltego_file_company)) | |
56 | check_files.update({"company": xml_company}) | |
57 | ||
58 | if maltego_file_dns in mtgl_file.namelist(): | |
59 | xml_dns = ET.parse(mtgl_file.open(maltego_file_dns)) | |
60 | check_files.update({"DNS": xml_dns}) | |
61 | ||
62 | if maltego_file_domain in mtgl_file.namelist(): | |
63 | xml_domain = ET.parse(mtgl_file.open(maltego_file_domain)) | |
64 | check_files.update({"domain": xml_domain}) | |
65 | ||
66 | if maltego_file_email in mtgl_file.namelist(): | |
67 | xml_email = ET.parse(mtgl_file.open(maltego_file_email)) | |
68 | check_files.update({"email": xml_email}) | |
69 | ||
70 | if maltego_file_ipv4 in mtgl_file.namelist(): | |
71 | xml_ipv4 = ET.parse(mtgl_file.open(maltego_file_ipv4)) | |
72 | check_files.update({"ipv4": xml_ipv4}) | |
73 | ||
74 | if maltego_file_location in mtgl_file.namelist(): | |
75 | xml_location = ET.parse(mtgl_file.open(maltego_file_location)) | |
76 | check_files.update({"location": xml_location}) | |
77 | ||
78 | if maltego_file_mxrecord in mtgl_file.namelist(): | |
79 | xml_mxrecord = ET.parse(mtgl_file.open(maltego_file_mxrecord)) | |
80 | check_files.update({"mxrecord": xml_mxrecord}) | |
81 | ||
82 | if maltego_file_nsrecord in mtgl_file.namelist(): | |
83 | xml_nsrecord = ET.parse(mtgl_file.open(maltego_file_nsrecord)) | |
84 | check_files.update({"nsrecord": xml_nsrecord}) | |
85 | ||
86 | if maltego_file_organization in mtgl_file.namelist(): | |
87 | xml_organization = ET.parse(mtgl_file.open(maltego_file_organization)) | |
88 | check_files.update({"organization": xml_organization}) | |
89 | ||
90 | if maltego_file_person in mtgl_file.namelist(): | |
91 | xml_person = ET.parse(mtgl_file.open(maltego_file_person)) | |
92 | check_files.update({"person": xml_person}) | |
93 | ||
94 | if maltego_file_number in mtgl_file.namelist(): | |
95 | xml_number = ET.parse(mtgl_file.open(maltego_file_number)) | |
96 | check_files.update({"number": xml_number}) | |
97 | ||
98 | if maltego_file_website in mtgl_file.namelist(): | |
99 | xml_web = ET.parse(mtgl_file.open(maltego_file_website)) | |
100 | check_files.update({"web": xml_web}) | |
101 | ||
102 | except zipfile.BadZipFile: | |
40 | 103 | return None |
41 | 104 | |
42 | file.close() | |
43 | return xml | |
105 | mtgl_file.close() | |
106 | return check_files | |
44 | 107 | |
45 | 108 | |
46 | 109 | class Host(): |
58 | 121 | |
59 | 122 | class MaltegoMtgxParser(): |
60 | 123 | |
61 | def __init__(self, xml_file): | |
62 | ||
63 | self.xml = openMtgx(xml_file) | |
64 | ||
65 | self.nodes = self.xml.findall( | |
66 | "{http://graphml.graphdrawing.org/xmlns}graph/" | |
67 | "{http://graphml.graphdrawing.org/xmlns}node") | |
68 | ||
69 | self.edges = self.xml.findall( | |
70 | "{http://graphml.graphdrawing.org/xmlns}graph/" | |
71 | "{http://graphml.graphdrawing.org/xmlns}edge") | |
72 | ||
73 | self.list_hosts = [] | |
74 | self.relations = {} | |
124 | def __init__(self, xml_file, extension): | |
125 | ||
126 | if extension == '.mtgx': | |
127 | self.xml = readMtgx(xml_file) | |
128 | self.nodes = self.xml.findall( | |
129 | "{http://graphml.graphdrawing.org/xmlns}graph/" | |
130 | "{http://graphml.graphdrawing.org/xmlns}node") | |
131 | self.edges = self.xml.findall( | |
132 | "{http://graphml.graphdrawing.org/xmlns}graph/" | |
133 | "{http://graphml.graphdrawing.org/xmlns}edge") | |
134 | ||
135 | self.list_hosts = [] | |
136 | self.relations = {} | |
137 | elif extension == '.mtgl': | |
138 | self.xml = readMtgl(xml_file) | |
75 | 139 | |
76 | 140 | def getRelations(self): |
77 | 141 | """ |
99 | 163 | self.relations.update({target: values}) |
100 | 164 | |
101 | 165 | def getIpAndId(self, node): |
102 | ||
103 | 166 | # Find node ID and maltego entity |
104 | 167 | node_id = node.get("id") |
105 | 168 | entity = node.find( |
106 | 169 | "{http://graphml.graphdrawing.org/xmlns}data/" |
107 | 170 | "{http://maltego.paterva.com/xml/mtgx}MaltegoEntity") |
108 | ||
109 | 171 | # Check if is IPv4Address |
110 | 172 | if entity.get("type") != "maltego.IPv4Address": |
111 | 173 | return None |
188 | 250 | def getLocation(self, target_node): |
189 | 251 | |
190 | 252 | # Parse Location Entity |
191 | result = { | |
192 | "name": "", | |
193 | "area": "", | |
194 | "country_code": "", | |
195 | "longitude": "", | |
196 | "latitude": "", | |
197 | "area_2": ""} | |
253 | result = {"name": "", "area": "", "country_code": "", "longitude": "", "latitude": "", "area_2": ""} | |
198 | 254 | |
199 | 255 | # Get relations with other nodes |
200 | 256 | node_relations = self.relations[target_node.get("id")] |
258 | 314 | self.getRelations() |
259 | 315 | |
260 | 316 | for node in self.nodes: |
261 | ||
262 | 317 | # Get IP Address if not continue with other node... |
263 | 318 | result = self.getIpAndId(node) |
264 | 319 | if not result: |
266 | 321 | |
267 | 322 | # Create host with values by default |
268 | 323 | host = Host() |
269 | host.ip = result["ip"] | |
270 | host.node_id = result["node_id"] | |
324 | host.ip = result.get("ip") | |
325 | host.node_id = result.get("node_id") | |
271 | 326 | |
272 | 327 | # Get relations with other nodes |
273 | 328 | node_relations = self.relations[host.node_id] |
296 | 351 | |
297 | 352 | return self.list_hosts |
298 | 353 | |
299 | ||
300 | class MaltegoPlugin(PluginXMLFormat): | |
354 | def getInfoMtgl(self, xml, name): | |
355 | sample_value = xml.findall(f'Properties/Fields/Field[@name="{name}"]') | |
356 | for data in sample_value: | |
357 | mtgl_data = data.find('SampleValue').text | |
358 | return mtgl_data | |
359 | ||
360 | ||
361 | class MaltegoPlugin(PluginZipFormat): | |
301 | 362 | |
302 | 363 | def __init__(self): |
303 | 364 | super().__init__() |
304 | 365 | self.identifier_tag = "maltego" |
305 | 366 | self.id = "Maltego" |
306 | self.name = "Maltego MTGX Output Plugin" | |
367 | self.name = "Maltego MTGX & MTGL Output Plugin" | |
307 | 368 | self.plugin_version = "1.0.1" |
308 | 369 | self.version = "Maltego 3.6" |
309 | 370 | self.framework_version = "1.0.0" |
371 | self.extension = [".mtgl", ".mtgx"] | |
372 | self.files_list = {"Graphs/Graph1.graphml", "Entities/maltego.Company.entity", | |
373 | "Entities/maltego.DNSName.entity", "Entities/maltego.Domain.entity", | |
374 | "Entities/maltego.EmailAddress.entity", "Entities/maltego.IPv4Address.entity", | |
375 | "Entities/maltego.Location.entity", "Entities/maltego.MXRecord.entity", | |
376 | "Entities/maltego.Organization.entity", "Entities/maltego.NSRecord.entity", | |
377 | "Entities/maltego.Person.entity", "Entities/maltego.PhoneNumber.entity", | |
378 | "Entities/maltego.Website.entity", "Entities/maltego.Hash.entity", | |
379 | "Entities/maltego.hashtag.entity", "Entities/maltego.TwitterUserList.entity"} | |
310 | 380 | self.current_path = None |
311 | 381 | self.options = None |
312 | 382 | self._current_output = None |
313 | ||
314 | 383 | self._command_regex = re.compile( |
315 | 384 | r'^(sudo maltego|maltego|\.\/maltego).*?') |
316 | ||
317 | 385 | global current_path |
318 | 386 | |
319 | def parseOutputString(self, filename, debug=False): | |
320 | ||
321 | maltego_parser = MaltegoMtgxParser(filename) | |
322 | for host in maltego_parser.parse(): | |
323 | # Create host | |
324 | try: | |
325 | old_hostname = host.dns_name["value"] | |
326 | except: | |
327 | old_hostname = "unknown" | |
328 | ||
329 | host_id = self.createAndAddHost( | |
330 | name=host.ip) | |
331 | ||
332 | # Create interface | |
333 | try: | |
334 | network_segment = host.netblock["ipv4_range"] | |
335 | hostname_resolution = [host.dns_name["value"]] | |
336 | except: | |
337 | network_segment = "unknown" | |
338 | hostname_resolution = "unknown" | |
339 | ||
340 | interface_id = self.createAndAddInterface( | |
341 | host_id=host_id, | |
342 | name=host.ip, | |
343 | ipv4_address=host.ip, | |
344 | network_segment=network_segment, | |
345 | hostname_resolution=hostname_resolution) | |
346 | ||
347 | # Create note with NetBlock information | |
348 | if host.netblock: | |
349 | try: | |
350 | text = ( | |
351 | "Network owner:\n" + | |
352 | host.netblock["network_owner"] or "unknown" + | |
353 | "Country:\n" + host.netblock["country"] or "unknown") | |
354 | except: | |
355 | text = "unknown" | |
356 | ||
357 | self.createAndAddNoteToHost( | |
358 | host_id=host_id, | |
359 | name="Netblock Information", | |
360 | text=text.encode('ascii', 'ignore') | |
361 | ) | |
362 | ||
363 | # Create note with host location | |
364 | if host.location: | |
365 | try: | |
366 | text = ( | |
367 | "Location:\n" + | |
368 | host.location["name"] + | |
369 | "\nArea:\n" + | |
370 | host.location["area"] + | |
371 | "\nArea 2:\n" + | |
372 | host.location["area_2"] + | |
373 | "\nCountry_code:\n" + | |
374 | host.location["country_code"] + | |
375 | "\nLatitude:\n" + | |
376 | host.location["latitude"] + | |
377 | "\nLongitude:\n" + | |
378 | host.location["longitude"]) | |
379 | except: | |
380 | text = "unknown" | |
381 | ||
382 | self.createAndAddNoteToHost( | |
383 | host_id=host_id, | |
384 | name="Location Information", | |
385 | text=text.encode('ascii', 'ignore')) | |
386 | ||
387 | # Create service web server | |
388 | if host.website: | |
389 | try: | |
390 | description = "SSL Enabled: " + host.website["ssl_enabled"] | |
391 | except: | |
392 | description = "unknown" | |
393 | ||
394 | service_id = self.createAndAddServiceToInterface( | |
395 | host_id=host_id, | |
396 | interface_id=interface_id, | |
397 | name=host.website["name"], | |
398 | protocol="TCP:HTTP", | |
399 | ports=[80], | |
400 | description=description) | |
401 | ||
402 | try: | |
403 | text = "Urls:\n" + host.website["urls"] | |
404 | ||
405 | self.createAndAddNoteToService( | |
406 | host_id=host_id, | |
407 | service_id=service_id, | |
408 | name="URLs", | |
409 | text=text.encode('ascii', 'ignore')) | |
410 | except: | |
387 | def parseOutputString(self, output, debug=False): | |
388 | ||
389 | if 'Graphs/Graph1.graphml' in output.namelist(): | |
390 | maltego_parser = MaltegoMtgxParser(output, self.extension[1]) | |
391 | if not maltego_parser.parse(): | |
411 | 392 | pass |
412 | ||
413 | if host.mx_record: | |
414 | ||
415 | self.createAndAddServiceToInterface( | |
416 | host_id=host_id, | |
417 | interface_id=interface_id, | |
418 | name=host.mx_record["value"], | |
419 | protocol="SMTP", | |
420 | ports=[25], | |
421 | description="E-mail Server") | |
422 | ||
423 | if host.ns_record: | |
424 | ||
425 | self.createAndAddServiceToInterface( | |
426 | host_id=host_id, | |
427 | interface_id=interface_id, | |
428 | name=host.ns_record["value"], | |
429 | protocol="DNS", | |
430 | ports=[53], | |
431 | description="DNS Server") | |
432 | ||
433 | def processReport(self, filepath): | |
434 | self.parseOutputString(filepath) | |
435 | ||
436 | def processCommandString(self, username, current_path, command_string): | |
437 | pass | |
393 | else: | |
394 | for host in maltego_parser.parse(): | |
395 | if host.ip is None: | |
396 | ip = '0.0.0.0' | |
397 | else: | |
398 | 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") | |
462 | else: | |
463 | maltego_parser = MaltegoMtgxParser(output, self.extension[0]) | |
464 | if maltego_parser.xml.get('ipv4'): | |
465 | host_ip = maltego_parser.getInfoMtgl(maltego_parser.xml['ipv4'], 'ipv4-address') | |
466 | host_id = self.createAndAddHost(name=host_ip) | |
467 | else: | |
468 | host_id = self.createAndAddHost(name=self.name) | |
469 | 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) | |
477 | ||
478 | if maltego_parser.xml.get('location'): | |
479 | location_name = maltego_parser.getInfoMtgl(maltego_parser.xml['location'], 'location.name') | |
480 | location_area = maltego_parser.getInfoMtgl(maltego_parser.xml['location'], 'location.area') | |
481 | location_country = maltego_parser.getInfoMtgl(maltego_parser.xml['location'], 'countrycode') | |
482 | location_longitude = maltego_parser.getInfoMtgl(maltego_parser.xml['location'], 'longitude') | |
483 | location_latitude = maltego_parser.getInfoMtgl(maltego_parser.xml['location'], 'latitude') | |
484 | text = f'Location:\n {location_name} \n Area:\n {location_area} \nCountry_code:\n {location_country} ' \ | |
485 | f'\nLatitude:\n {location_latitude} \nLongitude:\n {location_longitude}' | |
486 | ||
487 | self.createAndAddNoteToHost(host_id=host_id, name="Location Information", | |
488 | text=text.encode('ascii', 'ignore')) | |
489 | else: | |
490 | self.createAndAddNoteToHost(host_id=host_id, name="Location Information", text="unknown") | |
491 | ||
492 | if maltego_parser.xml.get('web'): | |
493 | web_name = maltego_parser.getInfoMtgl(maltego_parser.xml['web'], 'fqdn') | |
494 | text = f'Urls: \n {web_name}' | |
495 | web_port = maltego_parser.getInfoMtgl(maltego_parser.xml['web'], 'ports') | |
496 | if web_port is None: | |
497 | web_port = 80 | |
498 | ||
499 | web_ssh = maltego_parser.getInfoMtgl(maltego_parser.xml['web'], 'website.ssl-enabled') | |
500 | description = f'SSL Enabled: {web_ssh}' | |
501 | ||
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) | |
505 | ||
506 | self.createAndAddNoteToService(host_id=host_id, service_id=service_id, name="URLs", | |
507 | text=text.encode('ascii', 'ignore')) | |
508 | ||
509 | if maltego_parser.xml.get('mxrecord'): | |
510 | mx_name = maltego_parser.getInfoMtgl(maltego_parser.xml['mxrecord'], 'fqdn') | |
511 | ||
512 | self.createAndAddServiceToInterface(host_id=host_id, interface_id=interface_id, name=mx_name, | |
513 | protocol="SMTP", ports=[25], description="E-mail Server") | |
514 | ||
515 | if maltego_parser.xml.get('nsrecord'): | |
516 | 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") | |
519 | ||
520 | ||
438 | 521 | |
439 | 522 | |
440 | 523 | def createPlugin(): |
441 | 524 | return MaltegoPlugin() |
442 | ||
443 | # I'm Py3 |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | 5 | import re |
6 | import os | |
7 | import random | |
8 | import socket | |
9 | 6 | from faraday_plugins.plugins.plugin import PluginBase |
7 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
10 | 8 | |
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | 9 | |
14 | 10 | __author__ = "Francisco Amato" |
15 | 11 | __copyright__ = "Copyright 2013, Faraday Project" |
41 | 37 | lines = xml_output.splitlines() |
42 | 38 | self.items = [] |
43 | 39 | |
44 | for l in lines: | |
40 | for line in lines: | |
45 | 41 | |
46 | reg = re.search( | |
47 | "ACCOUNT FOUND: \[([^$]+)\] Host: ([^$]+) User: ([^$]+) Password: ([^$]+) \[SUCCESS\]", | |
48 | l) | |
49 | ||
50 | print("REG" + str(reg)) | |
51 | ||
42 | reg = re.search("ACCOUNT FOUND: \[([^$]+)\] Host: ([^$]+) User: ([^$]+) Password: ([^$]+) \[SUCCESS\]", line) | |
52 | 43 | if reg: |
53 | 44 | |
54 | 45 | item = { |
57 | 48 | 'user': reg.group(3), |
58 | 49 | 'pass': reg.group(4) |
59 | 50 | } |
60 | ||
61 | print("ITEM" + str(item)) | |
62 | item['ip'] = self.getAddress(item['host']) | |
51 | item['ip'] = resolve_hostname(item['host']) | |
63 | 52 | item['port'] = self.srv[item['service']] |
64 | print("ITEM" + str(item)) | |
65 | 53 | self.items.append(item) |
66 | ||
67 | def getAddress(self, hostname): | |
68 | """ | |
69 | Returns remote IP address from hostname. | |
70 | """ | |
71 | try: | |
72 | return socket.gethostbyname(hostname) | |
73 | except socket.error as msg: | |
74 | return hostname | |
75 | 54 | |
76 | 55 | |
77 | 56 | class MedusaPlugin(PluginBase): |
86 | 65 | self.plugin_version = "0.0.1" |
87 | 66 | self.version = "2.1.1" |
88 | 67 | self.options = None |
89 | self._current_output = None | |
90 | self._current_path = None | |
91 | self._command_regex = re.compile( | |
92 | r'^(sudo medusa|sudo \.\/medusa|medusa|\.\/medusa).*?') | |
93 | ||
68 | self._command_regex = re.compile(r'^(sudo medusa|sudo \.\/medusa|medusa|\.\/medusa)\s+.*?') | |
94 | 69 | self.host = None |
95 | 70 | self.port = "" |
71 | self._use_temp_file = True | |
72 | self._temp_file_extension = "txt" | |
73 | self.xml_arg_re = re.compile(r"^.*(-O\s*[^\s]+).*$") | |
96 | 74 | |
97 | def parseOutputString(self, output, debug=False): | |
75 | def parseOutputString(self, output): | |
98 | 76 | """ |
99 | 77 | This method will discard the output the shell sends, it will read it from |
100 | 78 | the xml where it expects it to be present. |
112 | 90 | h_id, |
113 | 91 | item['ip'], |
114 | 92 | ipv4_address=item['ip'], |
115 | hostname_resolution=item['host']) | |
93 | hostname_resolution=[item['host']]) | |
116 | 94 | else: |
117 | 95 | i_id = self.createAndAddInterface( |
118 | 96 | h_id, |
119 | 97 | item['ip'], |
120 | 98 | ipv6_address=item['ip'], |
121 | hostname_resolution=item['host']) | |
99 | hostname_resolution=[item['host']]) | |
122 | 100 | |
123 | 101 | port = self.port if self.port else item['port'] |
124 | 102 | |
144 | 122 | |
145 | 123 | del parser |
146 | 124 | |
147 | xml_arg_re = re.compile(r"^.*(-O\s*[^\s]+).*$") | |
148 | ||
149 | 125 | def processCommandString(self, username, current_path, command_string): |
150 | ||
126 | super().processCommandString(username, current_path, command_string) | |
151 | 127 | self.port = "" |
152 | self._output_file_path = os.path.join( | |
153 | self.data_path, "medusa_output-%s.txt" % random.uniform(1, 10)) | |
154 | 128 | arg_match = self.xml_arg_re.match(command_string) |
155 | 129 | |
156 | 130 | mreg = re.search(r"\-n( |)([\d]+)", command_string) |
158 | 132 | self.port = mreg.group(2) |
159 | 133 | |
160 | 134 | if arg_match is None: |
161 | return re.sub( | |
162 | r"(^.*?medusa?)", r"\1 -O %s" % self._output_file_path, | |
163 | command_string) | |
135 | return re.sub(r"(^.*?medusa?)", r"\1 -O %s" % self._output_file_path, command_string) | |
164 | 136 | else: |
165 | return re.sub( | |
166 | arg_match.group(1), | |
167 | r"-O %s" % self._output_file_path, | |
168 | command_string) | |
137 | return re.sub(arg_match.group(1), r"-O %s" % self._output_file_path, command_string) | |
169 | 138 | |
170 | 139 | def _isIPV4(self, ip): |
171 | 140 | if len(ip.split(".")) == 4: |
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 | # I'm Py3⏎ |
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 | from faraday_plugins.plugins.plugin import PluginBase | |
7 | import re | |
8 | import os | |
9 | import socket | |
10 | ||
11 | ||
12 | ||
13 | current_path = os.path.abspath(os.getcwd()) | |
14 | ||
15 | __author__ = "Francisco Amato" | |
16 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
17 | __credits__ = ["Francisco Amato"] | |
18 | __license__ = "" | |
19 | __version__ = "1.0.0" | |
20 | __maintainer__ = "Francisco Amato" | |
21 | __email__ = "[email protected]" | |
22 | __status__ = "Development" | |
23 | ||
24 | ||
25 | class MetagoofilParser: | |
26 | """ | |
27 | The objective of this class is to parse an xml file generated by the metagoofil tool. | |
28 | ||
29 | TODO: Handle errors. | |
30 | TODO: Test metagoofil output version. Handle what happens if the parser doesn't support it. | |
31 | TODO: Test cases. | |
32 | ||
33 | @param metagoofil_filepath A proper simple report generated by metagoofil | |
34 | """ | |
35 | ||
36 | def __init__(self, output): | |
37 | ||
38 | self.items = [] | |
39 | ||
40 | mfile = open("/root/dev/faraday/trunk/src/del", "r") | |
41 | output = mfile.read() | |
42 | mfile.close() | |
43 | ||
44 | mregex = re.search( | |
45 | "\[\+\] List of paths and servers found:[-\s]+([^$]+)\[\+\] List of e-mails found:", output, re.M) | |
46 | if mregex is None: | |
47 | return | |
48 | ||
49 | self.users = mregex.group(1).split("\n") | |
50 | self.software = mregex.group(2).split("\n") | |
51 | self.servers = mregex.group(1).strip().split("\n") | |
52 | ||
53 | for line in self.servers: | |
54 | line = line.strip() | |
55 | item = {'host': line, 'ip': self.resolve(line)} | |
56 | self.items.append(item) | |
57 | ||
58 | def resolve(self, host): | |
59 | try: | |
60 | return socket.gethostbyname(host) | |
61 | except: | |
62 | pass | |
63 | return host | |
64 | ||
65 | ||
66 | class MetagoofilPlugin(PluginBase): | |
67 | """ | |
68 | Example plugin to parse metagoofil output. | |
69 | """ | |
70 | ||
71 | def __init__(self): | |
72 | super().__init__() | |
73 | self.id = "Metagoofil" | |
74 | self.name = "Metagoofil XML Output Plugin" | |
75 | self.plugin_version = "0.0.1" | |
76 | self.version = "2.2" | |
77 | self.options = None | |
78 | self._current_output = None | |
79 | self._current_path = None | |
80 | self._command_regex = re.compile( | |
81 | r'^(sudo metagoofil|metagoofil|sudo metagoofil\.py|metagoofil\.py|python metagoofil\.py|\.\/metagoofil\.py).*?') | |
82 | self._completition = { | |
83 | "": "metagoofil.py -d microsoft.com -t doc,pdf -l 200 -n 50 -o microsoftfiles -f results.html", | |
84 | "-d": "domain to search", | |
85 | "-t": "filetype to download (pdf,doc,xls,ppt,odp,ods,docx,xlsx,pptx)", | |
86 | "-l": "limit of results to search (default 200)", | |
87 | "-h": "work with documents in directory (use \"yes\" for local analysis)", | |
88 | "-n": "limit of files to download", | |
89 | "-o": "working directory", | |
90 | "-f": "output file", | |
91 | } | |
92 | ||
93 | global current_path | |
94 | ||
95 | def canParseCommandString(self, current_input): | |
96 | if self._command_regex.match(current_input.strip()): | |
97 | return True | |
98 | else: | |
99 | return False | |
100 | ||
101 | def parseOutputString(self, output, debug=False): | |
102 | """ | |
103 | This method will discard the output the shell sends, it will read it from | |
104 | the xml where it expects it to be present. | |
105 | ||
106 | NOTE: if 'debug' is true then it is being run from a test case and the | |
107 | output being sent is valid. | |
108 | """ | |
109 | ||
110 | def processCommandString(self, username, current_path, command_string): | |
111 | """ | |
112 | """ | |
113 | return None | |
114 | ||
115 | ||
116 | def createPlugin(): | |
117 | return MetagoofilPlugin() | |
118 | ||
119 | # I'm Py3 |
4 | 4 | """ |
5 | 5 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
6 | 6 | import re |
7 | import os | |
8 | import sys | |
7 | ||
9 | 8 | |
10 | 9 | try: |
11 | 10 | import xml.etree.cElementTree as ET |
16 | 15 | ETREE_VERSION = ET.VERSION |
17 | 16 | |
18 | 17 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
19 | ||
20 | current_path = os.path.abspath(os.getcwd()) | |
21 | 18 | |
22 | 19 | __author__ = "Francisco Amato" |
23 | 20 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
336 | 333 | self.version = "4.7.2" |
337 | 334 | self.framework_version = "1.0.0" |
338 | 335 | self.options = None |
339 | self._current_output = None | |
340 | 336 | self.target = None |
341 | self._command_regex = re.compile(r'^(metasploit|sudo metasploit|\.\/metasploit).*?') | |
342 | 337 | |
343 | 338 | |
344 | 339 | def parseOutputString(self, output, debug=False): |
405 | 400 | else: |
406 | 401 | return False |
407 | 402 | |
408 | def processCommandString(self, username, current_path, command_string): | |
409 | return None | |
410 | 403 | |
411 | 404 | def setHost(self): |
412 | 405 | pass |
114 | 114 | self.name = "ndiff" |
115 | 115 | self.plugin_version = "0.0.1" |
116 | 116 | self.version = "1.0.0" |
117 | self._command_regex = re.compile(r'^(sudo ndiff|ndiff).*?') | |
117 | self._command_regex = re.compile(r'^(sudo ndiff|ndiff)\s+.*?') | |
118 | 118 | |
119 | 119 | def parseOutputString(self, output, debug=False): |
120 | ||
121 | 120 | parser = NdiffXmlParser(output) |
122 | ||
123 | 121 | for host in parser.hostDiff: |
124 | ||
125 | 122 | if host.ip is None: |
126 | 123 | continue |
127 | ||
128 | 124 | if host.isNewHost: |
129 | 125 | hostId = self.createAndAddHost(host.ip, '') |
130 | ||
131 | 126 | description = '%s is a NEW host active.\n' % host.ip |
132 | 127 | for port in host.ports: |
133 | 128 | description += 'Port: %s/%s\n' % (port[0], port[1]) |
134 | ||
135 | 129 | self.createAndAddVulnToHost( |
136 | 130 | hostId, |
137 | 131 | 'New host active', |
140 | 134 | 'INFO' |
141 | 135 | ) |
142 | 136 | else: |
143 | ||
144 | 137 | if host.ports == []: |
145 | 138 | continue |
146 | ||
147 | 139 | hostId = self.createAndAddHost(host.ip, '') |
148 | ||
149 | 140 | description = 'New service/s found.\n' |
150 | 141 | for port in host.ports: |
151 | 142 | description += 'Port: %s/%s\n' % (port[0], port[1]) |
159 | 150 | ) |
160 | 151 | |
161 | 152 | def processCommandString(self, username, current_path, command_string): |
153 | super().processCommandString(username, current_path, command_string) | |
162 | 154 | if command_string.find('--xml') < 0: |
163 | return command_string + '--xml' | |
155 | return f"{command_string} --xml " | |
164 | 156 | |
165 | 157 | |
166 | 158 | def createPlugin(): |
0 | # dotnessus_v2.py | |
1 | # Python module to deal with Nessus .nessus (v2) files | |
2 | # http://code.google.com/p/pynessus/ | |
3 | # | |
4 | ||
5 | # Copyright (C) 2010 Dustin Seibel | |
6 | # | |
7 | # GNU General Public Licence (GPL) | |
8 | # | |
9 | # This program is free software; you can redistribute it and/or modify it under | |
10 | # the terms of the GNU General Public License as published by the Free Software | |
11 | # Foundation; either version 2 of the License, or (at your option) any later | |
12 | # version. | |
13 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
14 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
15 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
16 | # details. | |
17 | # You should have received a copy of the GNU General Public License along with | |
18 | # this program; if not, write to the Free Software Foundation, Inc., 59 Temple | |
19 | # Place, Suite 330, Boston, MA 02111-1307 USA | |
20 | # | |
21 | # 2011-03-12: 0.1.1: Initial version. | |
22 | from __future__ import absolute_import | |
23 | ||
24 | import sys | |
25 | import re | |
26 | import xml.etree.ElementTree as ET | |
27 | from datetime import datetime | |
28 | from io import StringIO, BytesIO | |
29 | ||
30 | ||
31 | # List all nodes in a ReportItem object that can have multiple values | |
32 | MULTI_VALUED_ATTS = [ | |
33 | 'cve', | |
34 | 'bid', | |
35 | 'xref', | |
36 | 'cvss_base_score' | |
37 | ] | |
38 | ||
39 | # HOST_(START|END) date format | |
40 | HOST_DATE_FORMAT = '%a %b %d %H:%M:%S %Y' | |
41 | ||
42 | # Regex defs | |
43 | re_ip = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') | |
44 | re_wmi_ip = re.compile( | |
45 | 'IPAddress/IPSubnet.*?(?P<value>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', re.I) | |
46 | re_wmi_man = re.compile( | |
47 | 'Computer Manufacturer : (?P<manufacturer>.*?)\n.*?Computer Model : (?P<model>.*?)\n.*?Computer Memory : (?P<memory>\d+)\s', re.I | re.M | re.S) | |
48 | re_shares = re.compile('- (?P<value>.*?)\n', re.I | re.M | re.S) | |
49 | re_local_admins = re.compile('- (?P<value>.*?)\s\(', re.I | re.M | re.S) | |
50 | re_wsus = re.compile('WUServer: (?P<wsus_server>.*?)\n.*?AUOptions: (?P<wsus_auoption>.*?)\n.*?Detect LastSuccessTime: (?P<wsus_lastdetect>.*?)\n.*?Download LastSuccessTime: (?P<wsus_lastdownload>.*?)\n.*?Install LastSuccessTime: (?P<wsus_lastinstall>.*?)\n.*?RebootRequired: (?P<wsus_rebootneeded>.*?)\n.*?ServiceStatus: (?P<wsus_auenabled>.*?)(\n|$)', re.I | re.M | re.S) | |
51 | re_unix_memory = re.compile('Total memory: (?P<memory>\d+)\s', re.I) | |
52 | re_unix_model = re.compile( | |
53 | 'Serial Number\s+: (?P<serial>.*?)\s.*?\nProduct Name\s+: (?P<model>.*?)(\n|$)', re.I | re.M) | |
54 | re_unix_cpu = re.compile('Current Speed\s+: (?P<cpu_speed>.*?)\s*\nManufacturer\s+: (?P<cpu_vendor>.*?)\s*\nFamily\s+: (?P<cpu_model>.*?)\s*\nExternal Clock\s+: (?P<cpu_externalclock>.*?)\s*\nVersion\s+: (?P<cpu_version>.*?)\s*\nType\s+: (?P<cpu_type>.*?)($|\s*\n)', re.I | re.M) | |
55 | ||
56 | # Plugin to regex map | |
57 | # Format is plugin_id: (attribute_name, regex_object, attribute_to_parse, | |
58 | # multi_valued) | |
59 | REGEX_MAP = { | |
60 | '24272': ('ips', re_wmi_ip, 'plugin_output', True), | |
61 | '25203': ('ips', re_ip, 'plugin_output', True), | |
62 | '24270': ('', re_wmi_man, 'description', False), | |
63 | '10395': ('shares', re_shares, 'plugin_output', True), | |
64 | '10902': ('local_admins', re_local_admins, 'plugin_output', True), | |
65 | '10860': ('local_users', re_local_admins, 'plugin_output', True), | |
66 | '55555': ('', re_wsus, 'description', False), | |
67 | '45433': ('', re_unix_memory, 'plugin_output', False), | |
68 | '35351': ('', re_unix_model, 'plugin_output', False), | |
69 | '45432': ('', re_unix_cpu, 'plugin_output', False), | |
70 | } | |
71 | ||
72 | # Local IP list | |
73 | LOCAL_IP_LIST = [ | |
74 | '0.0.0.0', | |
75 | '127.0.0.1', | |
76 | ] | |
77 | ||
78 | ||
79 | class Report: | |
80 | ||
81 | def __init__(self): | |
82 | self.name = None | |
83 | self.targets = [] | |
84 | self.scan_start = None | |
85 | self.scan_end = None | |
86 | ||
87 | def parse(self, xml_file, from_string=False): | |
88 | """Import .nessus file""" | |
89 | # Parse XML file | |
90 | #getLogger(self).debug("Parsing report start") | |
91 | if from_string: | |
92 | try: | |
93 | xml_file = BytesIO(xml_file) | |
94 | except Exception as e1: | |
95 | try: | |
96 | xml_file = StringIO(xml_file) | |
97 | except Exception as e2: | |
98 | raise Exception(str(e1) + "\n" + str(e2)) | |
99 | ||
100 | # Iterate through each host scanned and create objects for each | |
101 | for event, elem in ET.iterparse(xml_file): | |
102 | ||
103 | #getLogger(self).debug("Parsing elemn %s" % elem[0:20]) | |
104 | # Grab the report name from the Report element | |
105 | if event == 'end' and elem.tag == 'Report': | |
106 | self.name = elem.attrib.get('name') | |
107 | continue | |
108 | ||
109 | # Only process ReportHost elements | |
110 | elif event == 'end' and elem.tag != 'ReportHost': | |
111 | continue | |
112 | ||
113 | rh_obj = ReportHost(elem) | |
114 | if rh_obj: | |
115 | self.targets.append(rh_obj) | |
116 | ||
117 | # Update Report dates | |
118 | if not self.scan_start: | |
119 | self.scan_start = rh_obj.host_start | |
120 | if not self.scan_end: | |
121 | self.scan_end = rh_obj.host_end | |
122 | if rh_obj.get('host_start'): | |
123 | if rh_obj.host_start < self.scan_start: | |
124 | self.scan_start = rh_obj.host_start | |
125 | if rh_obj.host_end > self.scan_end: | |
126 | self.scan_end = rh_obj.host_end | |
127 | ||
128 | def __repr__(self): | |
129 | return "<Report: %s>" % self.name | |
130 | ||
131 | def get_target(self, name): | |
132 | """Returns a target object given a name""" | |
133 | for t in self.targets: | |
134 | if name.lower() == t.name.lower(): | |
135 | return t | |
136 | ||
137 | ||
138 | class ReportHost: | |
139 | ||
140 | def __init__(self, xml_report_host): | |
141 | self.name = None | |
142 | self.dead = False | |
143 | self.vulns = [] | |
144 | ||
145 | # Do a check to make sure it's well formed | |
146 | # ... | |
147 | ||
148 | # Get ReportHost name | |
149 | self.name = xml_report_host.attrib.get('name') | |
150 | ||
151 | # Get HostProperties tags | |
152 | for n in xml_report_host.findall('HostProperties/tag'): | |
153 | setattr(self, n.attrib.get('name'), n.text) | |
154 | ||
155 | #getLogger(self).debug("Parsing host start tag") | |
156 | tmp = Report() | |
157 | # Convert scan dates and check for dead status | |
158 | if self.get('HOST_START'): | |
159 | ||
160 | self.host_start = self.get('HOST_START') | |
161 | #getLogger(self).info("Host start found %s" % self.host_start) | |
162 | ||
163 | #self.host_start = datetime.strptime(self.get('HOST_START'), HOST_DATE_FORMAT) | |
164 | else: | |
165 | self.dead = True | |
166 | self.host_end = self.get('HOST_END') | |
167 | #self.host_end = datetime.strptime(self.get('HOST_END'), HOST_DATE_FORMAT) | |
168 | ||
169 | # Get all ReportItems | |
170 | for ri in xml_report_host.findall('ReportItem'): | |
171 | ri_obj = ReportItem(ri) | |
172 | if ri_obj: | |
173 | self.vulns.append(ri_obj) | |
174 | xml_report_host.clear() | |
175 | ||
176 | # Do an additional check for deadness | |
177 | for v in self.find_vuln(plugin_id='10180'): | |
178 | if 'dead' in str(v.get('plugin_output')): | |
179 | self.dead = True | |
180 | ||
181 | # Parse additional fields into host attributes | |
182 | for plugin_id in REGEX_MAP: | |
183 | att, regex, dest_att, multi = REGEX_MAP[plugin_id] | |
184 | vulns = self.find_vuln(plugin_id=plugin_id) | |
185 | ||
186 | # If multi flag is set, store results in a dict | |
187 | if multi: | |
188 | results = [] | |
189 | ||
190 | # Grab all plugins | |
191 | for v in vulns: | |
192 | if multi: | |
193 | setattr(self, att, regex.findall(v.get(dest_att))) | |
194 | else: | |
195 | plugin_output = v.get(dest_att) | |
196 | if not plugin_output: | |
197 | continue | |
198 | ||
199 | res = regex.search(v.get(dest_att)) | |
200 | if not res: | |
201 | continue | |
202 | ||
203 | # Check to see if named fields were given | |
204 | if res.groupdict(): | |
205 | # Store each named field as an attribute | |
206 | for k, v in res.groupdict().items(): | |
207 | setattr(self, k, v) | |
208 | ||
209 | # No named fields, just grab whatever matched | |
210 | else: | |
211 | setattr(self, att, res.group()) | |
212 | ||
213 | def __repr__(self): | |
214 | return "<ReportHost: %s>" % self.name | |
215 | ||
216 | def get(self, attr): | |
217 | """Returns attribute value if it exists""" | |
218 | try: | |
219 | return getattr(self, attr) | |
220 | except AttributeError: | |
221 | return None | |
222 | ||
223 | def find_vuln(self, **kwargs): | |
224 | """Find a ReportItem given the search params""" | |
225 | results = [] | |
226 | ||
227 | # Iterate through preferences | |
228 | for r in self.vulns: | |
229 | match = True | |
230 | # If one of the search criteria doesn't match, set the flag | |
231 | for k in kwargs: | |
232 | if kwargs.get(k) != r.get(k): | |
233 | match = False | |
234 | ||
235 | # If it's a match, add it to results | |
236 | if match: | |
237 | results.append(r) | |
238 | return results | |
239 | ||
240 | def get_ips(self, exclude_local=True): | |
241 | """Return a list of IPs for host""" | |
242 | ip_list = set() | |
243 | if re_ip.search(self.name): | |
244 | ip_list.add(self.name) | |
245 | if self.get('host-ip'): | |
246 | ip_list.add(self.get('host-ip')) | |
247 | if self.get('ips'): | |
248 | ip_list.update(self.ips) | |
249 | ||
250 | # If exclude_local is set, remove local IPs from list | |
251 | if exclude_local: | |
252 | for i in LOCAL_IP_LIST: | |
253 | if i in ip_list: | |
254 | ip_list.remove(i) | |
255 | ||
256 | return list(ip_list) | |
257 | ||
258 | def get_open_ports(self): | |
259 | """Returns a dict of open ports found""" | |
260 | results = {} | |
261 | ||
262 | # Fetch results | |
263 | vulns = self.find_vuln(plugin_id='0') | |
264 | ||
265 | # For each port, put it in a dict | |
266 | for v in vulns: | |
267 | proto = v.get('protocol') | |
268 | port = v.get('port') | |
269 | if proto not in results: | |
270 | results[proto] = [] | |
271 | results[proto].append(port) | |
272 | return results | |
273 | ||
274 | def get_name(self): | |
275 | """Returns a friendly name for host""" | |
276 | if re_ip.search(self.name): | |
277 | if self.get('netbios-name'): | |
278 | return self.get('netbios-name').lower() | |
279 | elif self.get('host-fqdn'): | |
280 | return self.get('host-fqdn').lower() | |
281 | else: | |
282 | return self.name | |
283 | else: | |
284 | return self.name | |
285 | ||
286 | ||
287 | class ReportItem: | |
288 | ||
289 | def __init__(self, xml_report_item): | |
290 | # Make sure object is well formed | |
291 | # ... | |
292 | ||
293 | # Get ReportItem attributes | |
294 | self.port = xml_report_item.attrib.get('port') | |
295 | self.svc_name = xml_report_item.attrib.get('svc_name') | |
296 | self.protocol = xml_report_item.attrib.get('protocol') | |
297 | self.severity = xml_report_item.attrib.get('severity') | |
298 | self.plugin_id = xml_report_item.attrib.get('pluginID') | |
299 | self.plugin_name = xml_report_item.attrib.get('pluginName') | |
300 | self.plugin_family = xml_report_item.attrib.get('pluginFamily') | |
301 | ||
302 | # Create multi-valued atts | |
303 | for m in MULTI_VALUED_ATTS: | |
304 | setattr(self, m, list()) | |
305 | ||
306 | # Get optional nodes | |
307 | for n in xml_report_item.getchildren(): | |
308 | # If it's a multi-valued att, append to list | |
309 | if n.tag in MULTI_VALUED_ATTS: | |
310 | v = getattr(self, n.tag) | |
311 | v.append(n.text.strip()) | |
312 | setattr(self, n.tag, v) | |
313 | continue | |
314 | ||
315 | # If it's not a multi-valued att, store it as a string | |
316 | if n.text is not None: | |
317 | setattr(self, n.tag, n.text.strip()) | |
318 | ||
319 | xml_report_item.clear() | |
320 | ||
321 | def __repr__(self): | |
322 | return "<ReportItem: %s/%s %s %s>" % (self.port, self.protocol, self.plugin_id, self.plugin_name) | |
323 | ||
324 | def get(self, attr): | |
325 | """Returns attribute value if it exists""" | |
326 | try: | |
327 | return getattr(self, attr) | |
328 | except AttributeError: | |
329 | return None | |
330 | # I'm Py3⏎ |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | import dateutil | |
6 | 7 | |
7 | 8 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
8 | import re | |
9 | import os | |
10 | import socket | |
11 | ||
12 | import faraday_plugins.plugins.repo.nessus.dotnessus_v2 as dotnessus_v2 | |
13 | ||
14 | ||
15 | ||
16 | current_path = os.path.abspath(os.getcwd()) | |
17 | ||
18 | __author__ = "Francisco Amato" | |
19 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" | |
20 | __credits__ = ["Francisco Amato"] | |
9 | import xml.etree.ElementTree as ET | |
10 | ||
11 | ||
12 | __author__ = "Blas" | |
13 | __copyright__ = "Copyright (c) 2019, Infobyte LLC" | |
14 | __credits__ = ["Blas"] | |
21 | 15 | __license__ = "" |
22 | 16 | __version__ = "1.0.0" |
23 | __maintainer__ = "Francisco Amato" | |
24 | __email__ = "[email protected]" | |
17 | __maintainer__ = "Blas" | |
18 | __email__ = "[email protected]" | |
25 | 19 | __status__ = "Development" |
26 | 20 | |
27 | 21 | |
37 | 31 | """ |
38 | 32 | |
39 | 33 | def __init__(self, output): |
40 | lists = output.split("\r\n") | |
41 | i = 0 | |
42 | self.items = [] | |
43 | if re.search("Could not reach", output) is not None: | |
44 | self.fail = True | |
45 | return | |
46 | ||
47 | for line in lists: | |
48 | if i > 8: | |
49 | item = {'link': line} | |
50 | self.items.append(item) | |
51 | i = i + 1 | |
34 | self.tree = ET.fromstring(output) | |
35 | self.tag_control = [] | |
36 | for x in self.tree: | |
37 | self.tag_control.append(x) | |
38 | if self.tree: | |
39 | self.policy = self.getPolicy(self.tree) | |
40 | self.report = self.getReport(self.tree) | |
41 | else: | |
42 | self.policy = None | |
43 | self.report = None | |
44 | ||
45 | def getPolicy(self, tree): | |
46 | policy_tree = tree.find('Policy') | |
47 | return Policy(policy_tree) | |
48 | ||
49 | def getReport(self, tree): | |
50 | report_tree = tree.find('Report') | |
51 | return Report(report_tree) | |
52 | ||
53 | ||
54 | class Policy(): | |
55 | def __init__(self, policy_node): | |
56 | self.node = policy_node | |
57 | self.policy_name = self.node.find('policyName').text | |
58 | self.preferences = self.getPreferences(self.node.find('Preferences')) | |
59 | self.family_selection = self.getFamilySelection(self.node.find('FamilySelection')) | |
60 | self.individual_plugin_selection = self.getIndividualPluginSelection( | |
61 | self.node.find('IndividualPluginSelection')) | |
62 | ||
63 | def getPreferences(self, preferences): | |
64 | server_preferences = preferences.find('ServerPreferences') | |
65 | plugins_preferences = preferences.find('PluginsPreferences') | |
66 | server_preferences_all = [] | |
67 | plugins_preferences_json = {} | |
68 | plugins_preferences_all = [] | |
69 | for sp in server_preferences: | |
70 | sp_value = sp.find('value').text | |
71 | sp_name = sp.find('name').text | |
72 | server_preferences_all.append("Server Preferences name: {}, Server Preferences value: {}".format(sp_name, | |
73 | sp_value)) | |
74 | for pp in plugins_preferences: | |
75 | for pp_detail in pp: | |
76 | plugins_preferences_json.setdefault(pp_detail.tag, pp_detail.text) | |
77 | plugins_preferences_all.append(plugins_preferences_json) | |
78 | return server_preferences_all, plugins_preferences_all | |
79 | ||
80 | def getFamilySelection(self, family): | |
81 | family_all = [] | |
82 | for f in family: | |
83 | family_name = f.find('FamilyName').text | |
84 | family_value = f.find('Status').text | |
85 | family_all.append("Family Name: {}, Family Value: {}".format(family_name, family_value)) | |
86 | return family_all | |
87 | ||
88 | def getIndividualPluginSelection(self, individual): | |
89 | item_plugin = [] | |
90 | for i in individual: | |
91 | plugin_id = i.find('PluginId').text | |
92 | plugin_name = i.find('PluginName').text | |
93 | plugin_family = i.find('Family').text | |
94 | plugin_status = i.find('Status').text | |
95 | item_plugin.append("Plugin ID: {}, Plugin Name: {}, Family: {}, Status: {}".format(plugin_id, plugin_name, | |
96 | plugin_family, | |
97 | plugin_status)) | |
98 | return item_plugin | |
99 | ||
100 | ||
101 | class Report(): | |
102 | def __init__(self, report_node): | |
103 | self.node = report_node | |
104 | self.report_name = self.node.attrib.get('name') | |
105 | self.report_host = self.node.find('ReportHost') | |
106 | self.report_desc = [] | |
107 | self.report_ip = [] | |
108 | self.report_serv = [] | |
109 | self.report_json = {} | |
110 | if self.report_host is not None: | |
111 | 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) | |
118 | self.report_json['ip'] = self.report_ip | |
119 | self.report_json['desc'] = self.report_desc | |
120 | self.report_json['serv'] = self.report_serv | |
121 | self.report_json['host_end'] = self.host_properties.get('HOST_END') | |
122 | ||
123 | else: | |
124 | self.report_host_ip = None | |
125 | self.host_properties = None | |
126 | self.report_item = None | |
127 | self.report_json = None | |
128 | ||
129 | def getreportitems(self, items): | |
130 | result_item = [] | |
131 | ||
132 | 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') | |
140 | if item.find('plugin_output') is not None: | |
141 | self.plugin_output = item.find('plugin_output').text | |
142 | else: | |
143 | self.plugin_output = "Not Description" | |
144 | if item.find('description') is not None: | |
145 | self.description = item.find('description').text | |
146 | 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 | |
153 | ||
154 | def getinfoitem(self, item): | |
155 | item_tags = {} | |
156 | for i in item: | |
157 | item_tags.setdefault(i.tag, i.text) | |
158 | return item_tags | |
159 | ||
160 | def gethosttag(self, tags): | |
161 | host_tags = {} | |
162 | for t in tags: | |
163 | host_tags.setdefault(t.attrib.get('name'), t.text) | |
164 | return host_tags | |
52 | 165 | |
53 | 166 | |
54 | 167 | class NessusPlugin(PluginXMLFormat): |
66 | 179 | self.version = "5.2.4" |
67 | 180 | self.framework_version = "1.0.1" |
68 | 181 | self.options = None |
69 | self._current_output = None | |
70 | self._current_path = None | |
71 | self._command_regex = re.compile( | |
72 | r'^(nessus|sudo nessus|\.\/nessus).*?') | |
73 | self.host = None | |
74 | self.port = None | |
75 | self.protocol = None | |
76 | self.fail = None | |
77 | ||
78 | ||
79 | def canParseCommandString(self, current_input): | |
80 | if self._command_regex.match(current_input.strip()): | |
81 | return True | |
82 | else: | |
83 | return False | |
84 | ||
85 | def parseOutputString(self, output, debug=False): | |
182 | ||
183 | def parseOutputString(self, output): | |
86 | 184 | """ |
87 | 185 | This method will discard the output the shell sends, it will read it from |
88 | 186 | the xml where it expects it to be present. |
90 | 188 | NOTE: if 'debug' is true then it is being run from a test case and the |
91 | 189 | output being sent is valid. |
92 | 190 | """ |
93 | p = dotnessus_v2.Report() | |
94 | 191 | try: |
95 | p.parse(output, from_string=True) | |
96 | except Exception as e: | |
97 | self.logger.error("Exception - %s", e) | |
98 | ||
99 | for t in p.targets: | |
100 | mac = "" | |
101 | host = "" | |
102 | ip = "" | |
103 | ||
104 | if t.get('mac-address'): | |
105 | mac = t.get('mac-address') | |
106 | if t.get('host-fqdn'): | |
107 | host = t.get('host-fqdn') | |
108 | if t.get('host-ip'): | |
109 | ip = t.get('host-ip') | |
110 | ||
111 | if not ip: | |
112 | if not t.get_ips(): | |
113 | continue | |
114 | ip = t.get_ips().pop() | |
115 | ||
116 | h_id = self.createAndAddHost(ip, t.get('operating-system'), hostnames=[host]) | |
117 | ||
118 | if self._isIPV4(ip): | |
119 | i_id = self.createAndAddInterface( | |
120 | h_id, ip, mac, ipv4_address=ip, hostname_resolution=[host]) | |
121 | else: | |
122 | i_id = self.createAndAddInterface( | |
123 | h_id, ip, mac, ipv6_address=ip, hostname_resolution=[host]) | |
124 | ||
125 | srv = {} | |
126 | web = False | |
127 | for v in t.vulns: | |
128 | external_id = "" | |
129 | ||
130 | external_id = v.get('plugin_id') | |
131 | ||
132 | desc = "" | |
133 | desc += v.get('description') if v.get('description') else "" | |
134 | resolution = "" | |
135 | resolution = v.get('solution') if v.get('solution') else "" | |
136 | ||
137 | data = "\nOutput: " + v.get('plugin_output') if v.get('plugin_output') else "" | |
138 | ||
139 | ref = [] | |
140 | if v.get('cve'): | |
141 | cves = v.get('cve') | |
142 | for cve in cves: | |
143 | #logger.debug('Appending %s', cve.encode("utf-8")) | |
144 | ref.append(cve.encode("utf-8").strip()) | |
145 | if v.get('bid'): | |
146 | bids = v.get('bid') | |
147 | for bid in bids: | |
148 | #logger.debug('Appending %s', bid.encode("utf-8")) | |
149 | ref.append("BID-%s" % bid.encode("utf-8").strip() ) | |
150 | if v.get('cvss_base_score'): | |
151 | ref.append("CVSS: " + ", ".join(v.get('cvss_base_score'))) | |
152 | if v.get('xref'): | |
153 | ref.append(", ".join(v.get('xref'))) | |
154 | if v.get('svc_name') == "general": | |
155 | v_id = self.createAndAddVulnToHost(h_id, v.get('plugin_name'), | |
156 | desc=desc, ref=ref, data=data, severity=v.get('severity'), resolution=resolution, external_id=external_id) | |
192 | parser = NessusParser(output) | |
193 | except: | |
194 | return None | |
195 | ||
196 | if parser.report.report_json is not None: | |
197 | run_date = parser.report.report_json.get('host_end') | |
198 | if run_date: | |
199 | run_date = dateutil.parser.parse(run_date) | |
200 | 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'] | |
157 | 203 | else: |
158 | ||
159 | s_id = self.createAndAddServiceToInterface(h_id, i_id, v.get('svc_name'), | |
160 | v.get( | |
161 | 'protocol'), | |
162 | ports=[ | |
163 | str(v.get('port'))], | |
164 | status="open") | |
165 | ||
166 | web = re.search(r'^(www|http)', v.get('svc_name')) | |
167 | if v.get('svc_name') in srv: | |
168 | srv[v.get('svc_name')] = 1 | |
169 | ||
170 | if web: | |
171 | v_id = self.createAndAddVulnWebToService(h_id, s_id, v.get('plugin_name'), | |
172 | desc=desc, data=data, website=host, severity=v.get('severity'), | |
173 | resolution=resolution, ref=ref, external_id=external_id) | |
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 | ||
222 | host_id = self.createAndAddHost(ip_host, os=os, hostnames=host_name, mac=mac) | |
223 | ||
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 | ||
262 | if 'cvss_base_score' in data: | |
263 | cvss_base_score = "CVSS :{}".format(data['cvss_base_score']) | |
264 | ref.append(cvss_base_score) | |
265 | else: | |
266 | ref = [] | |
267 | ||
268 | policyviolations = [] | |
269 | if serv[6] == 'Policy Compliance': | |
270 | # 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 | ||
275 | for policy_check_data in bis_benchmark_data: | |
276 | if 'ref.' in policy_check_data: | |
277 | 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 | ||
285 | self.createAndAddVulnToHost(host_id, | |
286 | vulnerability_name, | |
287 | desc=desc, | |
288 | severity=risk_factor, | |
289 | resolution=resolution, | |
290 | data=data_po, | |
291 | ref=ref, | |
292 | policyviolations=policyviolations, | |
293 | external_id=external_id, | |
294 | run_date=run_date) | |
174 | 295 | else: |
175 | v_id = self.createAndAddVulnToService(h_id, s_id, v.get('plugin_name'), | |
176 | desc=desc, data=data, severity=v.get('severity'), resolution=resolution, | |
177 | ref=ref, external_id=external_id) | |
178 | ||
179 | def _isIPV4(self, ip): | |
180 | if len(ip.split(".")) == 4: | |
181 | return True | |
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 | ||
321 | if 'cvss_base_score' in data: | |
322 | cvss_base_score = f"CVSS:{data['cvss_base_score']}" | |
323 | ref.append(cvss_base_score) | |
324 | if 'cvss_vector' in data: | |
325 | cvss_vector = f"CVSSVECTOR:{data['cvss_vector']}" | |
326 | ref.append(cvss_vector) | |
327 | if 'see_also' in data: | |
328 | ref.append(data['see_also']) | |
329 | if 'cpe' in data: | |
330 | ref.append(data['cpe']) | |
331 | if 'xref' in data: | |
332 | ref.append(data['xref']) | |
333 | ||
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': | |
341 | self.createAndAddVulnWebToService(host_id, | |
342 | service_id, | |
343 | name=vulnerability_name, | |
344 | desc=desc, | |
345 | data=data_po, | |
346 | severity=risk_factor, | |
347 | resolution=resolution, | |
348 | ref=ref, | |
349 | external_id=external_id, | |
350 | website=website, | |
351 | run_date=run_date) | |
352 | else: | |
353 | self.createAndAddVulnToService(host_id, | |
354 | service_id, | |
355 | name=vulnerability_name, | |
356 | severity=risk_factor, | |
357 | desc=desc, | |
358 | ref=ref, | |
359 | data=data_po, | |
360 | external_id=external_id, | |
361 | resolution=resolution, | |
362 | run_date=run_date) | |
182 | 363 | else: |
183 | return False | |
184 | ||
185 | def processCommandString(self, username, current_path, command_string): | |
186 | return None | |
187 | ||
188 | def setHost(self): | |
189 | pass | |
190 | ||
191 | def resolve(self, host): | |
192 | try: | |
193 | return socket.gethostbyname(host) | |
194 | except: | |
195 | pass | |
196 | return host | |
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") | |
197 | 372 | |
198 | 373 | |
199 | 374 | def createPlugin(): |
200 | 375 | return NessusPlugin() |
201 | ||
202 | # I'm Py3 |
0 | # pynessus.py | |
1 | # Python module to interact with a Nessus 4.x scanner via XMLRPC. | |
2 | # http://code.google.com/p/pynessus/ | |
3 | # | |
4 | # Copyright (C) 2010 Dustin Seibel | |
5 | # | |
6 | # GNU General Public Licence (GPL) | |
7 | # | |
8 | # This program is free software; you can redistribute it and/or modify it under | |
9 | # the terms of the GNU General Public License as published by the Free Software | |
10 | # Foundation; either version 2 of the License, or (at your option) any later | |
11 | # version. | |
12 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
14 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
15 | # details. | |
16 | # You should have received a copy of the GNU General Public License along with | |
17 | # this program; if not, write to the Free Software Foundation, Inc., 59 Temple | |
18 | # Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | # | |
20 | # 2010-08-12: 0.1.0: Initial version. | |
21 | # 2011-03-12: 0.2.1: Added a bunch of methods and robustified everything. | |
22 | import ctypes | |
23 | import sys | |
24 | try: | |
25 | from urllib2 import ProxyHandler, build_opener, urlopen, install_opener | |
26 | from urlparse import urljoin | |
27 | from urllib import quote | |
28 | except ImportError: | |
29 | from urllib.request import ProxyHandler, build_opener, urlopen, install_opener | |
30 | from urllib.parse import urljoin, quote | |
31 | ||
32 | import xml.etree.ElementTree as ET | |
33 | import re | |
34 | import datetime | |
35 | import os | |
36 | from random import randint | |
37 | ||
38 | # Regex defs | |
39 | re_unix_timestamp = re.compile('^\d{10}$') | |
40 | re_unauthorized = re.compile('<title>200 Unauthorized</title>') | |
41 | ||
42 | TOKEN_FILE = '.nessus_token' | |
43 | ||
44 | # Plugin multi-value tags | |
45 | PLUGIN_MULTI_VAL = [ | |
46 | 'bid', | |
47 | 'xref', | |
48 | 'cve', | |
49 | ] | |
50 | ||
51 | ||
52 | class NessusServer: | |
53 | ||
54 | def __init__(self, server, port, username, password, verbose=False): | |
55 | self.server = server | |
56 | self.port = port | |
57 | self.username = username | |
58 | self.password = password | |
59 | self.base_url = 'https://%s:%s' % (self.server, self.port) | |
60 | self.verbose = verbose | |
61 | self.launched_scans = {} | |
62 | ||
63 | # Force urllib2 to not use a proxy | |
64 | hand = ProxyHandler({}) | |
65 | opener = build_opener(hand) | |
66 | install_opener(opener) | |
67 | ||
68 | self.login() | |
69 | ||
70 | # If token file exists, use it | |
71 | #self.token = get_token_file() | |
72 | # if not self.check_auth(): | |
73 | # self.login() | |
74 | # success = create_token_file(self.token) | |
75 | # # if not success... | |
76 | ||
77 | def login(self): | |
78 | """Login to server""" | |
79 | # If token file exists, try to use it | |
80 | self.token = get_token_file() | |
81 | if self.check_auth(): | |
82 | return True | |
83 | ||
84 | # Make call to server | |
85 | data = make_args(login=self.username, password=quote(self.password)) | |
86 | resp = self._call('login', data) | |
87 | if self.verbose: | |
88 | print(resp) | |
89 | ||
90 | # Parse token | |
91 | seq, status, parsed = parse_reply(resp, ['token']) | |
92 | if 'token' in parsed: | |
93 | self.token = parsed['token'] | |
94 | else: | |
95 | return False | |
96 | ||
97 | # Store it on the filesystem | |
98 | success = create_token_file(self.token) | |
99 | if success: | |
100 | return True | |
101 | else: | |
102 | return False | |
103 | ||
104 | def logout(self): | |
105 | """Logout from server""" | |
106 | data = make_args(token=self.token) | |
107 | resp = self._call('logout', data) | |
108 | self.token = None | |
109 | ||
110 | def check_auth(self): | |
111 | """Does a quick check to make sure token is still valid""" | |
112 | if not self.token: | |
113 | return False | |
114 | data = make_args(token=self.token) | |
115 | resp = self._call('scan/list', data) | |
116 | if not resp: | |
117 | return False | |
118 | elif re_unauthorized.search(resp): | |
119 | return False | |
120 | else: | |
121 | return True | |
122 | ||
123 | def download_plugins(self): | |
124 | """Downloads all plugins""" | |
125 | data = make_args(token=self.token) | |
126 | resp = self._call('plugins/descriptions', data) | |
127 | ||
128 | # Get parsed data | |
129 | keys = [] | |
130 | seq, status, parsed = parse_reply( | |
131 | resp, keys, uniq='pluginID', start_node='pluginsList') | |
132 | return parsed | |
133 | ||
134 | def download_report(self, uuid, v1=False): | |
135 | """Retrieves a report""" | |
136 | if v1: | |
137 | data = make_args(token=self.token, report=uuid, v1='true') | |
138 | else: | |
139 | data = make_args(token=self.token, report=uuid) | |
140 | url = urljoin(self.base_url, 'file/report/download/?%s' % data) | |
141 | req = urlopen(url) | |
142 | resp = req.read() | |
143 | if not check_auth(resp): | |
144 | print("Unauthorized", file=sys.stderr) | |
145 | return None | |
146 | return resp | |
147 | ||
148 | def launch_scan(self, name, policy_id, target_list): | |
149 | """Launches scan. Returns UUID of scan.""" | |
150 | arg_targets = quote('\n'.join(target_list)) | |
151 | data = make_args(token=self.token, scan_name=quote( | |
152 | name), policy_id=policy_id, target=arg_targets) | |
153 | resp = self._call('/scan/new', data) | |
154 | if self.verbose: | |
155 | print(resp) | |
156 | ||
157 | # Get parsed data | |
158 | keys = ['uuid', 'owner', 'start_time', 'scan_name'] | |
159 | seq, status, parsed = parse_reply(resp, keys) | |
160 | self.launched_scans[parsed['uuid']] = parsed | |
161 | return parsed['uuid'] | |
162 | ||
163 | def list_plugins(self): | |
164 | """List plugins""" | |
165 | data = make_args(token=self.token) | |
166 | resp = self._call('plugins/list', data) | |
167 | ||
168 | def list_policies(self): | |
169 | """List policies""" | |
170 | data = make_args(token=self.token) | |
171 | resp = self._call('policy/list', data) | |
172 | ||
173 | # Get parsed data | |
174 | seq, status, parsed = parse_reply( | |
175 | resp, ['policyName', 'policyOwner', 'policyComments'], uniq='policyID') | |
176 | return parsed | |
177 | ||
178 | def get_policy_id(self, policy_name): | |
179 | """Attempts to grab the policy ID for a name""" | |
180 | pols = self.list_policies() | |
181 | for k, v in pols.items(): | |
182 | if v.get('policyName').lower() == policy_name: | |
183 | return k | |
184 | ||
185 | def list_reports(self): | |
186 | """List reports""" | |
187 | data = make_args(token=self.token) | |
188 | resp = self._call('report/list', data) | |
189 | ||
190 | # Get parsed data | |
191 | seq, status, parsed = parse_reply( | |
192 | resp, ['name', 'readableName', 'timestamp', 'status'], uniq='name') | |
193 | return parsed | |
194 | ||
195 | def list_scans(self): | |
196 | """List scans""" | |
197 | data = make_args(token=self.token) | |
198 | resp = self._call('scan/list', data) | |
199 | ||
200 | # Get parsed data | |
201 | keys = ['owner', 'start_time', | |
202 | 'completion_current', 'completion_total'] | |
203 | seq, status, parsed = parse_reply( | |
204 | resp, keys, uniq='uuid', start_node='scans/scanList') | |
205 | return parsed | |
206 | ||
207 | def list_hosts(self, report_uuid): | |
208 | """List hosts for a given report""" | |
209 | data = make_args(token=self.token, report=report_uuid) | |
210 | resp = self._call('report/hosts', data) | |
211 | ||
212 | # Get parsed data | |
213 | keys = ['hostname', 'severity'] | |
214 | seq, status, parsed = parse_reply( | |
215 | resp, keys, uniq='hostname', start_node='hostList') | |
216 | return parsed | |
217 | ||
218 | def list_ports(self, report_uuid, hostname): | |
219 | """List hosts for a given report""" | |
220 | data = make_args(token=self.token, report=report_uuid, | |
221 | hostname=hostname) | |
222 | resp = self._call('report/ports', data) | |
223 | # return resp | |
224 | ||
225 | # Get parsed data | |
226 | seq, status, parsed = parse_ports(resp) | |
227 | return parsed | |
228 | ||
229 | def list_detail(self, report_uuid, hostname, protocol, port): | |
230 | """List details for a given host/protocol/port""" | |
231 | data = make_args(token=self.token, report=report_uuid, | |
232 | hostname=hostname, protocol=protocol, port=port) | |
233 | resp = self._call('report/detail', data) | |
234 | # return resp | |
235 | ||
236 | # Get parsed data | |
237 | seq, status, parsed = parse_ports(resp) | |
238 | return parsed | |
239 | ||
240 | def list_tags(self, report_uuid, hostname): | |
241 | """List hosts for a given report""" | |
242 | data = make_args(token=self.token, report=report_uuid, | |
243 | hostname=hostname) | |
244 | resp = self._call('report/tags', data) | |
245 | # return resp | |
246 | ||
247 | # Get parsed data | |
248 | seq, status, tags = parse_tags(resp) | |
249 | return tags | |
250 | ||
251 | # Template methods | |
252 | def create_template(self, name, policy_id, target_list): | |
253 | """Creates a new scan template. Returns """ | |
254 | arg_targets = quote('\n'.join(target_list)) | |
255 | data = make_args(token=self.token, template_name=quote( | |
256 | name), policy_id=policy_id, target=arg_targets) | |
257 | resp = self._call('/scan/template/new', data) | |
258 | ||
259 | def edit_template(self, template_id, name, policy_id, target_list): | |
260 | """Edits an existing scan template.""" | |
261 | arg_targets = quote('\n'.join(target_list)) | |
262 | data = make_args(token=self.token, template=template_id, template_name=quote( | |
263 | name), policy_id=policy_id, target=arg_targets) | |
264 | resp = self._call('/scan/template/edit', data) | |
265 | ||
266 | def list_templates(self): | |
267 | """List templates""" | |
268 | data = make_args(token=self.token) | |
269 | resp = self._call('scan/list', data) | |
270 | ||
271 | # Get parsed data | |
272 | keys = ['policy_id', 'readableName', 'owner', 'startTime'] | |
273 | seq, status, parsed = parse_reply( | |
274 | resp, keys, uniq='name', start_node='templates') | |
275 | return parsed | |
276 | ||
277 | def _call(self, func_url, args): | |
278 | url = urljoin(self.base_url, func_url) | |
279 | if self.verbose: | |
280 | print("URL: '%s'" % url) | |
281 | print("POST: '%s'" % args) | |
282 | req = urlopen(url, args) | |
283 | resp = req.read() | |
284 | if not check_auth(resp): | |
285 | print("200 Unauthorized", file=sys.stderr) | |
286 | return resp | |
287 | return resp | |
288 | ||
289 | ||
290 | def check_auth(resp_str): | |
291 | """Checks for an unauthorized message in HTTP response.""" | |
292 | if re_unauthorized.search(resp_str): | |
293 | return False | |
294 | else: | |
295 | return True | |
296 | ||
297 | ||
298 | def create_token_file(token, token_file=TOKEN_FILE): | |
299 | """Creates token file""" | |
300 | if not token: | |
301 | return False | |
302 | # Write to file | |
303 | try: | |
304 | fout = open(token_file, 'w') | |
305 | except IOError: | |
306 | return False | |
307 | fout.write(token) | |
308 | fout.close() | |
309 | ||
310 | # Confirm the file was created and has the right token | |
311 | new_token = get_token_file(token_file) | |
312 | if new_token != token: | |
313 | return False | |
314 | else: | |
315 | return True | |
316 | ||
317 | ||
318 | def get_token_file(token_file=TOKEN_FILE): | |
319 | """Checks token from file""" | |
320 | if not os.path.isfile(token_file): | |
321 | return False | |
322 | fin = open(token_file, 'r') | |
323 | token = fin.read() | |
324 | fin.close() | |
325 | return token | |
326 | ||
327 | ||
328 | def convert_date(unix_timestamp): | |
329 | """Converts UNIX timestamp to a datetime object""" | |
330 | # try: | |
331 | # return datetime.datetime.fromtimestamp(float(unix_timestamp)) | |
332 | # except Exception: | |
333 | # return unix_timestamp | |
334 | return datetime.datetime.fromtimestamp(float(unix_timestamp)) | |
335 | ||
336 | ||
337 | def parse_reply(xml_string, key_list, start_node=None, uniq=None): | |
338 | """Gets all key/value pairs from XML""" | |
339 | ROOT_NODES = ['seq', 'status', 'contents'] | |
340 | if not xml_string: | |
341 | return (0, 'Not a valid string', {}) | |
342 | ||
343 | # Parse xml | |
344 | try: | |
345 | xml = ET.fromstring(xml_string) | |
346 | except ET.ExpatError: | |
347 | return (0, 'Cannot parse XML', {}) | |
348 | ||
349 | # Make sure it looks like what we expect it to be | |
350 | if [t.tag for t in xml.getchildren()] != ROOT_NODES: | |
351 | return (0, 'XML not formatted correctly', {}) | |
352 | ||
353 | # Get seq and status | |
354 | seq = xml.findtext('seq') | |
355 | status = xml.findtext('status') | |
356 | ||
357 | # If start node was given, append it to contents node | |
358 | if start_node: | |
359 | start_node = 'contents/%s' % start_node | |
360 | else: | |
361 | start_node = 'contents' | |
362 | if not xml.find(start_node): | |
363 | return (seq, 'start_node not found', {}) | |
364 | ||
365 | # If a unique value was given, make sure it is a valid tag | |
366 | if uniq: | |
367 | found = False | |
368 | for x in xml.find(start_node).getiterator(): | |
369 | if x.tag == uniq: | |
370 | found = True | |
371 | break | |
372 | if not found: | |
373 | return (seq, 'uniq not a valid tag', {}) | |
374 | ||
375 | # Parse keys from contents | |
376 | d = {} | |
377 | for x in xml.find(start_node).getiterator(): | |
378 | if uniq: | |
379 | # If tag is a unique field, start a new dict | |
380 | if x.tag == uniq: | |
381 | d[x.text] = {} | |
382 | k = x.text | |
383 | ||
384 | # Store key/value pair if tag is in key list or if no key list was | |
385 | # given | |
386 | if not x.text: | |
387 | continue | |
388 | if ((x.tag in key_list) or (not key_list)) and x.text.strip(): | |
389 | # If the tag has the word time and the value is a UNIX | |
390 | # timestamp, convert it | |
391 | if 'time' in x.tag and re_unix_timestamp.search(x.text): | |
392 | d[k][x.tag] = convert_date(x.text) | |
393 | else: | |
394 | # Check to see if this is multi-valued | |
395 | if x.tag in PLUGIN_MULTI_VAL: | |
396 | if x.tag in d[k]: | |
397 | d[k][x.tag].append(x.text) | |
398 | else: | |
399 | d[k][x.tag] = [x.text] | |
400 | else: | |
401 | d[k][x.tag] = x.text | |
402 | ||
403 | else: | |
404 | # Store key/value pair if tag is in key list | |
405 | if not x.text: | |
406 | continue | |
407 | if ((x.tag in key_list) or (not key_list)) and x.text.strip(): | |
408 | # If the tag has the word time and the value is a UNIX | |
409 | # timestamp, convert it | |
410 | if 'time' in x.tag and re_unix_timestamp.search(x.text): | |
411 | d[x.tag] = convert_date(x.text) | |
412 | else: | |
413 | d[x.tag] = x.text | |
414 | return (seq, status, d) | |
415 | ||
416 | ||
417 | def parse_reply_orig(xml_string, key_list, start_node=None, uniq=None): | |
418 | """Gets all key/value pairs from XML""" | |
419 | ROOT_NODES = ['seq', 'status', 'contents'] | |
420 | if not xml_string: | |
421 | return (0, 'Not a valid string', {}) | |
422 | ||
423 | # Parse xml | |
424 | try: | |
425 | xml = ET.fromstring(xml_string) | |
426 | except ET.ExpatError: | |
427 | return (0, 'Cannot parse XML', {}) | |
428 | ||
429 | # Make sure it looks like what we expect it to be | |
430 | if [t.tag for t in xml.getchildren()] != ROOT_NODES: | |
431 | return (0, 'XML not formatted correctly', {}) | |
432 | ||
433 | # Get seq and status | |
434 | seq = xml.findtext('seq') | |
435 | status = xml.findtext('status') | |
436 | ||
437 | # If start node was given, append it to contents node | |
438 | if start_node: | |
439 | start_node = 'contents/%s' % start_node | |
440 | else: | |
441 | start_node = 'contents' | |
442 | if not xml.find(start_node): | |
443 | return (seq, 'start_node not found', {}) | |
444 | ||
445 | # If a unique value was given, make sure it is a valid tag | |
446 | if uniq: | |
447 | found = False | |
448 | for x in xml.find(start_node).getiterator(): | |
449 | if x.tag == uniq: | |
450 | found = True | |
451 | break | |
452 | if not found: | |
453 | return (seq, 'uniq not a valid tag', {}) | |
454 | ||
455 | # Parse keys from contents | |
456 | d = {} | |
457 | for x in xml.find(start_node).getiterator(): | |
458 | if uniq: | |
459 | # If tag is a unique field, start a new dict | |
460 | if x.tag == uniq: | |
461 | d[x.text] = {} | |
462 | k = x.text | |
463 | ||
464 | # Store key/value pair if tag is in key list | |
465 | if x.tag in key_list: | |
466 | # If the tag has the word time and the value is a UNIX | |
467 | # timestamp, convert it | |
468 | if 'time' in x.tag and re_unix_timestamp.search(x.text): | |
469 | d[k][x.tag] = convert_date(x.text) | |
470 | else: | |
471 | d[k][x.tag] = x.text | |
472 | ||
473 | else: | |
474 | # Store key/value pair if tag is in key list | |
475 | if x.tag in key_list: | |
476 | # If the tag has the word time and the value is a UNIX | |
477 | # timestamp, convert it | |
478 | if 'time' in x.tag and re_unix_timestamp.search(x.text): | |
479 | d[x.tag] = convert_date(x.text) | |
480 | else: | |
481 | d[x.tag] = x.text | |
482 | return (seq, status, d) | |
483 | ||
484 | ||
485 | def parse_ports(xml_string): | |
486 | """Parses ports from report/ports""" | |
487 | ROOT_NODES = ['seq', 'status', 'contents'] | |
488 | if not xml_string: | |
489 | return (0, 'Not a valid string', {}) | |
490 | ||
491 | # Parse xml | |
492 | try: | |
493 | xml = ET.fromstring(xml_string) | |
494 | except ET.ExpatError: | |
495 | return (0, 'Cannot parse XML', {}) | |
496 | ||
497 | # Make sure it looks like what we expect it to be | |
498 | if [t.tag for t in xml.getchildren()] != ROOT_NODES: | |
499 | return (0, 'XML not formatted correctly', {}) | |
500 | ||
501 | # Get seq and status | |
502 | seq = xml.findtext('seq') | |
503 | status = xml.findtext('status') | |
504 | ||
505 | # Parse ports | |
506 | d = {'tcp': {}, 'udp': {}, 'icmp': {}} | |
507 | for t in xml.findall('contents/portList/port'): | |
508 | port_d = {} | |
509 | prot = t.findtext('protocol') | |
510 | num = t.findtext('portNum') | |
511 | ||
512 | # Get additional attributes | |
513 | port_d['severity'] = t.findtext('severity') | |
514 | port_d['svcName'] = t.findtext('svcName') | |
515 | ||
516 | d[prot][num] = port_d | |
517 | return (seq, status, d) | |
518 | ||
519 | ||
520 | def parse_tags(xml_string): | |
521 | """Parses tags from report/tags""" | |
522 | ROOT_NODES = ['seq', 'status', 'contents'] | |
523 | if not xml_string: | |
524 | return (0, 'Not a valid string', {}) | |
525 | ||
526 | # Parse xml | |
527 | try: | |
528 | xml = ET.fromstring(xml_string) | |
529 | except ET.ExpatError: | |
530 | return (0, 'Cannot parse XML', {}) | |
531 | ||
532 | # Make sure it looks like what we expect it to be | |
533 | if [t.tag for t in xml.getchildren()] != ROOT_NODES: | |
534 | return (0, 'XML not formatted correctly', {}) | |
535 | ||
536 | # Get seq and status | |
537 | seq = xml.findtext('seq') | |
538 | status = xml.findtext('status') | |
539 | ||
540 | # Parse tags | |
541 | d = {} | |
542 | for t in xml.findall('contents/tags/tag'): | |
543 | k = t.findtext('name') | |
544 | v = t.findtext('value') | |
545 | d[k] = v | |
546 | return (seq, status, d) | |
547 | ||
548 | ||
549 | def make_args(**kwargs): | |
550 | """Returns arg list suitable for GET or POST requests""" | |
551 | args = [] | |
552 | for k in kwargs: | |
553 | args.append('%s=%s' % (k, str(kwargs[k]))) | |
554 | ||
555 | # Add a random number | |
556 | seq = randint(1, 1000) | |
557 | args.append('seq=%d' % seq) | |
558 | ||
559 | return '&'.join(args) | |
560 | ||
561 | ||
562 | def zerome(string): | |
563 | # taken from http://www.codexon.com/posts/clearing-passwords-in-memory-with-python | |
564 | # to be used to secure the password in memory | |
565 | # find the header size with a dummy string | |
566 | temp = "finding offset" | |
567 | header = ctypes.string_at(id(temp), sys.getsizeof(temp)).find(temp) | |
568 | ||
569 | location = id(string) + header | |
570 | size = sys.getsizeof(string) - header | |
571 | ||
572 | # Check platform | |
573 | if 'windows' in sys.platform.lower(): | |
574 | memset = ctypes.cdll.msvcrt.memset | |
575 | else: | |
576 | # For Linux, use the following. Change the 6 to whatever it is on your | |
577 | # computer. | |
578 | memset = ctypes.CDLL("libc.so.6").memset | |
579 | ||
580 | print("Clearing 0x%08x size %i bytes" % (location, size)) | |
581 | ||
582 | memset(location, 0, size) | |
583 | ||
584 | ||
585 | # I'm Py3 |
17 | 17 | |
18 | 18 | def __init__(self): |
19 | 19 | super().__init__() |
20 | self.id = "Netdiscover" | |
21 | self.name = "netdiscover" | |
20 | self.id = "Netdiscover" | |
21 | self.name = "netdiscover" | |
22 | 22 | self.plugin_version = "0.0.1" |
23 | self.version = "1.0.0" | |
24 | self._command_regex = re.compile(r'^(sudo netdiscover|netdiscover).*?') | |
23 | self.version = "1.0.0" | |
24 | self._command_regex = re.compile(r'^(sudo netdiscover|netdiscover)\s+.*?') | |
25 | 25 | |
26 | def parseOutputString(self, output, debug=False): | |
26 | def parseOutputString(self, output): | |
27 | 27 | #regexp get ip, mac and hostname |
28 | 28 | reg = re.findall(r"(([0-9]+\.?){4})\s+(([0-9a-f]+\:?){6})((\s+[0-9]+){2})(.*)", output) |
29 | 29 | |
39 | 39 | |
40 | 40 | return True |
41 | 41 | |
42 | def processCommandString(self, username, current_path, command_string): | |
43 | return None | |
44 | 42 | |
45 | 43 | |
46 | 44 | def createPlugin(): |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | import re | |
7 | from bs4 import BeautifulSoup | |
6 | 8 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
7 | import re | |
8 | import os | |
9 | import socket | |
10 | from bs4 import BeautifulSoup | |
9 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | 10 | |
12 | 11 | try: |
13 | 12 | import xml.etree.cElementTree as ET |
19 | 18 | |
20 | 19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
21 | 20 | |
22 | current_path = os.path.abspath(os.getcwd()) | |
23 | 21 | |
24 | 22 | __author__ = "Francisco Amato" |
25 | 23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
121 | 119 | self.resolution = self.get_text_from_subnode("actionsToTake") |
122 | 120 | self.request = self.get_text_from_subnode("rawrequest") |
123 | 121 | self.response = self.get_text_from_subnode("rawresponse") |
124 | if self.response: | |
125 | self.response = self.response.encode(encoding,errors="backslashreplace").decode(encoding) | |
126 | if self.request: | |
127 | self.request = self.request.encode(encoding,errors="backslashreplace").decode(encoding) | |
128 | if self.reference: | |
129 | self.reference = self.reference.encode(encoding,errors="backslashreplace").decode(encoding) | |
130 | ||
131 | ||
132 | 122 | self.kvulns = [] |
133 | 123 | for v in self.node.findall("knownvulnerabilities/knownvulnerability"): |
134 | 124 | self.node = v |
158 | 148 | if self.owasp: |
159 | 149 | self.ref.append("OWASP-" + self.owasp) |
160 | 150 | if self.reference: |
161 | self.ref.extend(list(set(re.findall('https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+', self.reference)))) | |
151 | self.ref.extend(sorted(set(re.findall('https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+', self.reference)))) | |
162 | 152 | if self.cvss: |
163 | 153 | self.ref.append(self.cvss) |
164 | 154 | |
202 | 192 | self.version = "Netsparker 3.1.1.0" |
203 | 193 | self.framework_version = "1.0.0" |
204 | 194 | self.options = None |
205 | self._current_output = None | |
206 | self._command_regex = re.compile( | |
207 | r'^(sudo netsparker|\.\/netsparker).*?') | |
208 | ||
209 | ||
210 | def resolve(self, host): | |
211 | try: | |
212 | return socket.gethostbyname(host) | |
213 | except: | |
214 | pass | |
215 | return host | |
216 | ||
217 | def parseOutputString(self, output, debug=False): | |
218 | ||
195 | ||
196 | def parseOutputString(self, output): | |
219 | 197 | parser = NetsparkerXmlParser(output) |
220 | 198 | first = True |
221 | 199 | for i in parser.items: |
222 | 200 | if first: |
223 | ip = self.resolve(i.hostname) | |
201 | ip = resolve_hostname(i.hostname) | |
224 | 202 | h_id = self.createAndAddHost(ip, hostnames=[ip]) |
225 | 203 | |
226 | 204 | s_id = self.createAndAddServiceToHost(h_id, str(i.port), |
245 | 223 | |
246 | 224 | del parser |
247 | 225 | |
248 | def processCommandString(self, username, current_path, command_string): | |
249 | return None | |
250 | ||
251 | 226 | |
252 | 227 | def createPlugin(): |
253 | 228 | return NetsparkerPlugin() |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | import re | |
7 | from urllib.parse import urlparse | |
6 | 8 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
7 | import re | |
8 | import os | |
9 | import socket | |
9 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
10 | 10 | |
11 | 11 | try: |
12 | 12 | import xml.etree.cElementTree as ET |
18 | 18 | |
19 | 19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
20 | 20 | |
21 | current_path = os.path.abspath(os.getcwd()) | |
22 | 21 | |
23 | 22 | __author__ = "Francisco Amato" |
24 | 23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
30 | 29 | __status__ = "Development" |
31 | 30 | |
32 | 31 | |
33 | def cleaner_unicode(string): | |
34 | if string is not None: | |
35 | return string.encode('ascii', errors='backslashreplace') | |
32 | ||
33 | def get_urls(string): | |
34 | if isinstance(string, bytes): | |
35 | string_decode = string.decode("utf-8") | |
36 | urls = re.findall('(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+', string_decode) | |
36 | 37 | else: |
37 | return string | |
38 | ||
39 | def cleaner_results(string): | |
40 | ||
41 | try: | |
42 | q = re.compile(r'<.*?>', re.IGNORECASE) | |
43 | return re.sub(q, '', string) | |
44 | ||
45 | except: | |
46 | return '' | |
47 | ||
48 | def get_urls(string): | |
49 | urls = re.findall(r'href=[\'"]?([^\'" >]+)', string) | |
38 | urls = re.findall('(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+', string) | |
50 | 39 | return urls |
51 | 40 | |
52 | 41 | |
63 | 52 | |
64 | 53 | def __init__(self, xml_output): |
65 | 54 | self.filepath = xml_output |
66 | ||
67 | 55 | tree = self.parse_xml(xml_output) |
68 | 56 | if tree: |
69 | 57 | self.items = [data for data in self.get_items(tree)] |
84 | 72 | except SyntaxError as err: |
85 | 73 | self.devlog("SyntaxError: %s. %s" % (err, xml_output)) |
86 | 74 | return None |
87 | ||
88 | 75 | return tree |
89 | 76 | |
90 | 77 | def get_items(self, tree): |
107 | 94 | return "high" |
108 | 95 | return severity |
109 | 96 | |
110 | def __init__(self, item_node): | |
97 | def __init__(self, item_node, encoding="ascii"): | |
111 | 98 | self.node = item_node |
112 | self.url = self.get_text_from_subnode("url") | |
113 | ||
114 | host = re.search( | |
115 | "(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", self.url) | |
116 | ||
117 | self.protocol = host.group(1) | |
118 | self.hostname = host.group(4) | |
119 | self.port = 80 | |
120 | ||
121 | if self.protocol == 'https': | |
122 | self.port = 443 | |
123 | if host.group(11) is not None: | |
124 | self.port = host.group(11) | |
125 | ||
99 | self.url = urlparse(self.get_text_from_subnode("url")) | |
100 | self.protocol = self.url.scheme | |
101 | self.hostname = self.url.netloc | |
102 | self.port = self.url.port | |
103 | if self.port is None: | |
104 | self.port = '80' | |
126 | 105 | self.type = self.get_text_from_subnode("type") |
127 | 106 | self.name = self.get_text_from_subnode("name") |
128 | 107 | self.severity = self.re_map_severity(self.get_text_from_subnode("severity")) |
129 | 108 | self.certainty = self.get_text_from_subnode("certainty") |
130 | ||
131 | ||
132 | 109 | self.node = item_node.find("http-request") |
133 | 110 | self.method = self.get_text_from_subnode("method") |
134 | 111 | self.request = self.get_text_from_subnode("content") |
135 | ||
136 | #print self.node | |
137 | 112 | self.param = "" |
138 | 113 | self.paramval = "" |
139 | 114 | for p in self.node.findall("parameters/parameter"): |
142 | 117 | |
143 | 118 | self.node = item_node.find("http-response") |
144 | 119 | self.response = self.get_text_from_subnode("content") |
145 | ||
146 | 120 | self.extra = [] |
147 | 121 | for v in item_node.findall("extra-information/info"): |
148 | 122 | self.extra.append(v.get('name') + ":" + v.get('value') ) |
158 | 132 | |
159 | 133 | self.ref = [] |
160 | 134 | if self.cwe: |
161 | self.ref.append("CWE-" + self.cwe) | |
135 | self.ref.append("CWE-{}".format(self.cwe)) | |
162 | 136 | if self.owasp: |
163 | self.ref.append("OWASP-" + self.owasp) | |
137 | self.ref.append("OWASP-{}".format(self.owasp)) | |
164 | 138 | |
165 | 139 | self.node = item_node |
166 | 140 | self.remedyreferences = self.get_text_from_subnode("remedy-references") |
172 | 146 | for u in get_urls(self.externalreferences): |
173 | 147 | self.ref.append(u) |
174 | 148 | |
175 | self.impact = cleaner_results(self.get_text_from_subnode("impact")) | |
176 | self.remedialprocedure = cleaner_results(self.get_text_from_subnode("remedial-procedure")) | |
177 | self.remedialactions = cleaner_results(self.get_text_from_subnode("remedial-actions")) | |
178 | self.exploitationskills = cleaner_results(self.get_text_from_subnode("exploitation-skills")) | |
179 | self.proofofconcept = cleaner_results(self.get_text_from_subnode("proof-of-concept")) | |
180 | ||
181 | self.resolution = self.remedialprocedure | |
182 | self.resolution += "\nRemedial Actions: " + self.remedialactions if self.remedialactions is not None else "" | |
183 | ||
184 | ||
185 | self.desc = cleaner_results(self.get_text_from_subnode("description")) | |
186 | self.desc += "\nImpact: " + self.impact if self.impact else "" | |
187 | self.desc += "\nExploitation Skills: " + self.exploitationskills if self.exploitationskills else "" | |
188 | self.desc += "\nProof of concept: " + self.proofofconcept if self.proofofconcept else "" | |
189 | self.desc += "\nWASC: " + self.wasc if self.wasc else "" | |
190 | self.desc += "\nPCI31: " + self.pci if self.pci else "" | |
191 | self.desc += "\nPCI32: " + self.pci2 if self.pci2 else "" | |
192 | self.desc += "\nCAPEC: " + self.capec if self.capec else "" | |
193 | self.desc += "\nHIPA: " + self.hipaa if self.hipaa else "" | |
194 | self.desc += "\nExtra: " + "\n".join(self.extra) if self.extra else "" | |
149 | self.impact = self.get_text_from_subnode("impact") | |
150 | self.remedialprocedure = self.get_text_from_subnode("remedial-procedure") | |
151 | self.remedialactions = self.get_text_from_subnode("remedial-actions") | |
152 | self.exploitationskills = self.get_text_from_subnode("exploitation-skills") | |
153 | self.proofofconcept = self.get_text_from_subnode("proof-of-concept") | |
154 | ||
155 | self.resolution = "Remerdial Procedure: {} \nRemedial Actions: {}".format(self.remedialprocedure, | |
156 | self.remedialactions) | |
157 | ||
158 | self.desc = self.get_text_from_subnode("description") | |
159 | self.desc = "\nImpact: {} \nExploitation Skills: {} \nProof of concept: {} \nWASC: {} \nPCI31: {} \nPCI32: {}" \ | |
160 | " \nCAPEC: {} \nHIPA: {} \nExtra: {}".format(self.impact, self.exploitationskills, | |
161 | self.proofofconcept, self.wasc, self.pci, self.pci2, | |
162 | self.capec, self.hipaa, self.extra) | |
195 | 163 | |
196 | 164 | def get_text_from_subnode(self, subnode_xpath_expr): |
197 | 165 | """ |
202 | 170 | if self.node: |
203 | 171 | sub_node = self.node.find(subnode_xpath_expr) |
204 | 172 | if sub_node is not None: |
205 | if sub_node.text is not None: | |
206 | return cleaner_unicode(sub_node.text) | |
207 | ||
208 | return "" | |
173 | return sub_node.text | |
174 | return None | |
209 | 175 | |
210 | 176 | |
211 | 177 | class NetsparkerCloudPlugin(PluginXMLFormat): |
222 | 188 | self.version = "NetsparkerCloud" |
223 | 189 | self.framework_version = "1.0.0" |
224 | 190 | self.options = None |
225 | self._current_output = None | |
226 | self._command_regex = re.compile( | |
227 | r'^(sudo netsparkercloud|\.\/netsparkercloud).*?') | |
228 | ||
229 | ||
230 | def resolve(self, host): | |
231 | try: | |
232 | return socket.gethostbyname(host) | |
233 | except: | |
234 | pass | |
235 | return host | |
236 | 191 | |
237 | 192 | def parseOutputString(self, output, debug=False): |
238 | ||
239 | 193 | parser = NetsparkerCloudXmlParser(output) |
240 | 194 | first = True |
241 | 195 | for i in parser.items: |
242 | 196 | if first: |
243 | ip = self.resolve(i.hostname) | |
197 | ip = resolve_hostname(i.hostname) | |
244 | 198 | h_id = self.createAndAddHost(ip) |
245 | i_id = self.createAndAddInterface( | |
246 | h_id, ip, ipv4_address=ip, hostname_resolution=[i.hostname]) | |
247 | ||
248 | s_id = self.createAndAddServiceToInterface(h_id, i_id, str(i.port), | |
249 | str(i.protocol), | |
250 | ports=[str(i.port)], | |
251 | status="open") | |
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") | |
252 | 201 | |
253 | 202 | first = False |
254 | ||
255 | 203 | v_id = self.createAndAddVulnWebToService(h_id, s_id, i.name, ref=i.ref, website=i.hostname, |
256 | severity=i.severity, desc=i.desc, path=i.url, method=i.method, | |
204 | severity=i.severity, desc=i.desc, path=i.url.path, method=i.method, | |
257 | 205 | request=i.request, response=i.response, resolution=i.resolution, |
258 | 206 | pname=i.param) |
259 | ||
260 | 207 | del parser |
261 | 208 | |
262 | def processCommandString(self, username, current_path, command_string): | |
263 | return None | |
264 | 209 | |
265 | 210 | def setHost(self): |
266 | 211 | pass |
268 | 213 | |
269 | 214 | def createPlugin(): |
270 | 215 | return NetsparkerCloudPlugin() |
271 |
5 | 5 | """ |
6 | 6 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
7 | 7 | import re |
8 | import os | |
9 | 8 | |
10 | 9 | try: |
11 | 10 | import xml.etree.cElementTree as ET |
19 | 18 | |
20 | 19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
21 | 20 | |
22 | current_path = os.path.abspath(os.getcwd()) | |
23 | 21 | |
24 | 22 | __author__ = "Micaela Ranea Sanchez" |
25 | 23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
64 | 62 | try: |
65 | 63 | tree = ET.fromstring(xml_output) |
66 | 64 | except SyntaxError as err: |
67 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
68 | 65 | return None |
69 | 66 | |
70 | 67 | return tree |
171 | 168 | if item.tag == 'exploits': |
172 | 169 | for exploit in list(item): |
173 | 170 | if exploit.get('title') and exploit.get('link') and exploit.get('type') \ |
174 | and exploit.get('sklLevel'): | |
175 | title = exploit.get('title').encode( | |
176 | "ascii", errors="backslashreplace").strip() | |
177 | link = exploit.get('link').encode( | |
178 | "ascii", errors="backslashreplace").strip() | |
179 | type = exploit.get('type').encode( | |
180 | "ascii", errors="backslashreplace").strip() | |
181 | skillLevel = exploit.get('sklLevel').encode( | |
182 | "ascii", errors="backslashreplace").strip() | |
183 | vuln['refs'].append(title + b' ' + link + b' ' + type + b' ' + skillLevel) | |
171 | and exploit.get('skillLevel'): | |
172 | title = exploit.get('title').strip() | |
173 | link = exploit.get('link').strip() | |
174 | type = exploit.get('type').strip() | |
175 | skillLevel = exploit.get('skillLevel').strip() | |
176 | vuln['refs'].append(" ".join([title, link, type, skillLevel])) | |
184 | 177 | if item.tag == 'malware': |
185 | 178 | for names in item.findall("name"): |
186 | 179 | nameMalware = names.text |
188 | 181 | if item.tag == 'references': |
189 | 182 | for ref in list(item): |
190 | 183 | if ref.text: |
191 | rf = ref.text.encode( | |
192 | "ascii", errors="backslashreplace").strip() | |
184 | rf = ref.text.strip() | |
193 | 185 | vuln['refs'].append(rf) |
194 | 186 | if item.tag == 'solution': |
195 | 187 | for htmlType in list(item): |
216 | 208 | host['name'] = node.get('address') |
217 | 209 | host['mac'] = node.get('hardware-address') |
218 | 210 | host['hostnames'] = list() |
219 | host['os'] = list() | |
211 | host['os'] = "" | |
220 | 212 | host['services'] = list() |
221 | host['fingerprints'] = list() | |
222 | host['fingerprints_software'] = list() | |
223 | 213 | host['vulns'] = self.parse_tests_type(node, vulns) |
224 | host['scan-template'] = node.get('scan-template') | |
225 | host['scan-name'] = node.get('scan-name') | |
226 | host['scan-importance'] = node.get('scan-importance') | |
227 | host['risk-score'] = node.get('risk-score') | |
228 | 214 | |
229 | 215 | for names in node.iter('names'): |
230 | 216 | for name in list(names): |
232 | 218 | |
233 | 219 | for fingerprints in node.iter('fingerprints'): |
234 | 220 | for os_data in fingerprints.iter('os'): |
235 | data = { | |
236 | 'certainty': os_data.get('certainty'), | |
237 | 'vendor': os_data.get('vendor'), | |
238 | 'family': os_data.get('family'), | |
239 | 'product': os_data.get('product'), | |
240 | 'version': os_data.get('version'), | |
241 | 'arch': os_data.get('arch'), | |
242 | 'device-class': os_data.get('device-class'), | |
243 | } | |
244 | host['os'].append(data) | |
245 | ||
246 | for fingerprints_tag in fingerprints.iter('fingerprint'): | |
247 | data_fingerprints_tag = { | |
248 | 'certainty': fingerprints_tag.get('certainty'), | |
249 | 'product': fingerprints_tag.get('product'), | |
250 | 'version': fingerprints_tag.get('version'), | |
251 | } | |
252 | host['fingerprints'].append(data_fingerprints_tag) | |
253 | ||
221 | os_name = os_data.get('product') | |
222 | if os_name: | |
223 | host['os'] = os_name | |
254 | 224 | for endpoints in node.iter('endpoints'): |
255 | 225 | for endpoint in list(endpoints): |
256 | 226 | svc = { |
268 | 238 | if "banner" in config.get('name'): |
269 | 239 | svc['version'] = config.get('name') |
270 | 240 | host['services'].append(svc) |
271 | ||
272 | for softwaretag in node.iter('software'): | |
273 | for soft_data in softwaretag.iter('fingerprint'): | |
274 | data_soft = { | |
275 | 'certainty': soft_data.get('certainty'), | |
276 | 'vendor': soft_data.get('vendor'), | |
277 | 'family': soft_data.get('family'), | |
278 | 'product': soft_data.get('product'), | |
279 | 'version': soft_data.get('version'), | |
280 | } | |
281 | host['fingerprints_software'].append(data_soft) | |
282 | 241 | hosts.append(host) |
283 | 242 | |
284 | 243 | return hosts |
298 | 257 | self.version = "Nexpose Enterprise 5.7.19" |
299 | 258 | self.framework_version = "1.0.0" |
300 | 259 | self.options = None |
301 | self._current_output = None | |
302 | self._command_regex = re.compile(r'^(sudo nexpose|\.\/nexpose).*?') | |
303 | 260 | |
304 | 261 | def parseOutputString(self, output, debug=False): |
305 | 262 | |
306 | 263 | parser = NexposeFullXmlParser(output) |
307 | 264 | |
308 | 265 | for item in parser.items: |
309 | h_id = self.createAndAddHost(item['name'], item['os'], hostnames=item['hostnames'], | |
310 | scan_template=item['scan-template'], site_name=item['scan-name'], | |
311 | site_importance=item['scan-importance'], risk_score=item['risk-score'], | |
312 | fingerprints=item['fingerprints'], | |
313 | fingerprints_software=item['fingerprints_software'] | |
314 | ) | |
266 | h_id = self.createAndAddHost(item['name'], item['os'], hostnames=item['hostnames']) | |
315 | 267 | pattern = '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$' |
316 | 268 | if not item['mac']: |
317 | 269 | item['mac'] = '0000000000000000' |
324 | 276 | item['name'], |
325 | 277 | mac=item['mac'], |
326 | 278 | ipv4_address=item['name'], |
327 | hostname_resolution=item['hostnames'], | |
328 | scan_template=item['scan-template'], | |
329 | site_name=item['scan-name'], | |
330 | site_importance=item['scan-importance'], | |
331 | risk_score=item['risk-score'], | |
332 | fingerprints=item['fingerprints'], | |
333 | fingerprints_software=item['fingerprints_software'], | |
279 | hostname_resolution=item['hostnames'] | |
334 | 280 | ) |
335 | 281 | else: |
336 | 282 | i_id = self.createAndAddInterface( |
348 | 294 | v['desc'], |
349 | 295 | v['refs'], |
350 | 296 | v['severity'], |
351 | v['resolution'], | |
352 | v['vulnerable_since'], | |
353 | v['scan_id'], | |
354 | v['pci'] | |
297 | v['resolution'] | |
355 | 298 | ) |
356 | 299 | |
357 | 300 | for s in item['services']: |
378 | 321 | v['refs'], |
379 | 322 | v['severity'], |
380 | 323 | v['resolution'], |
381 | v['risk'], | |
382 | 324 | path=v.get('path', '')) |
383 | 325 | else: |
384 | 326 | v_id = self.createAndAddVulnToService( |
388 | 330 | v['desc'], |
389 | 331 | v['refs'], |
390 | 332 | v['severity'], |
391 | v['resolution'], | |
392 | v['risk'] | |
333 | v['resolution'] | |
393 | 334 | ) |
394 | 335 | |
395 | 336 | del parser |
396 | 337 | |
397 | def processCommandString(self, username, current_path, command_string): | |
398 | return None | |
399 | 338 | |
400 | 339 | def setHost(self): |
401 | 340 | pass |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | 5 | import re |
6 | import os | |
7 | import random | |
8 | 6 | from html.parser import HTMLParser |
9 | 7 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
10 | 8 | from faraday_plugins.plugins import plugins_utils |
20 | 18 | |
21 | 19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
22 | 20 | |
23 | current_path = os.path.abspath(os.getcwd()) | |
24 | 21 | |
25 | 22 | __author__ = "Francisco Amato" |
26 | 23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
257 | 254 | self.plugin_version = "0.0.2" |
258 | 255 | self.version = "2.1.5" |
259 | 256 | self.options = None |
260 | self._current_output = None | |
261 | 257 | self.parent = None |
258 | self._use_temp_file = True | |
259 | self._temp_file_extension = "xml" | |
260 | self.xml_arg_re = re.compile(r"^.*(-output\s*[^\s]+).*$") | |
262 | 261 | self._command_regex = re.compile( |
263 | r'^(sudo nikto|nikto|sudo nikto\.pl|nikto\.pl|perl nikto\.pl|\.\/nikto\.pl|\.\/nikto).*?') | |
262 | r'^(sudo nikto|nikto|sudo nikto\.pl|nikto\.pl|perl nikto\.pl|\.\/nikto\.pl|\.\/nikto)\s+.*?') | |
264 | 263 | self._completition = { |
265 | 264 | "": "", |
266 | 265 | "-ask+": "Whether to ask about submitting updates", |
343 | 342 | |
344 | 343 | del parser |
345 | 344 | |
346 | xml_arg_re = re.compile(r"^.*(-output\s*[^\s]+).*$") | |
345 | ||
347 | 346 | |
348 | 347 | def processCommandString(self, username, current_path, command_string): |
349 | 348 | """ |
350 | 349 | Adds the -oX parameter to get xml output to the command string that the |
351 | 350 | user has set. |
352 | 351 | """ |
353 | self._output_file_path = os.path.join( | |
354 | self.data_path, | |
355 | "%s_%s_output-%s.xml" % ( | |
356 | self.get_ws(), | |
357 | self.id, | |
358 | random.uniform(1, 10) | |
359 | ) | |
360 | ) | |
352 | super().processCommandString(username, current_path, command_string) | |
361 | 353 | |
362 | 354 | arg_match = self.xml_arg_re.match(command_string) |
363 | 355 | |
364 | 356 | if arg_match is None: |
365 | return re.sub(r"(^.*?nikto(\.pl)?)", | |
366 | r"\1 -output %s -Format XML" % self._output_file_path, | |
367 | command_string) | |
357 | return re.sub(r"(^.*?nikto(\.pl)?)", r"\1 -output %s -Format XML" % self._output_file_path, command_string) | |
368 | 358 | else: |
369 | 359 | data = re.sub(" \-Format XML", "", command_string) |
370 | return re.sub(arg_match.group(1), | |
371 | r"-output %s -Format XML" % self._output_file_path, | |
372 | data) | |
360 | return re.sub(arg_match.group(1), r"-output %s -Format XML" % self._output_file_path, data) | |
373 | 361 | |
374 | 362 | def setHost(self): |
375 | 363 | pass |
7 | 7 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
8 | 8 | import re |
9 | 9 | import os |
10 | import sys | |
11 | import random | |
12 | 10 | |
13 | 11 | |
14 | 12 | try: |
401 | 399 | name = service_node.get("name") |
402 | 400 | self.name = name if name else 'unknown' |
403 | 401 | |
402 | self.tunnel = service_node.get("tunnel") | |
403 | if self.tunnel == "ssl": | |
404 | if self.name == "http": | |
405 | self.name = "https" | |
406 | elif self.name == 'imap': | |
407 | self.name = 'imaps' | |
408 | elif self.name == 'pop3': | |
409 | self.name = 'pop3s' | |
410 | ||
404 | 411 | product = service_node.get("product") |
405 | 412 | self.product = product if product else 'unknown' |
406 | 413 | |
430 | 437 | self.framework_version = "1.0.0" |
431 | 438 | self.options = None |
432 | 439 | self._current_output = None |
433 | self._command_regex = re.compile(r'^(sudo nmap|nmap|\.\/nmap).*?') | |
434 | ||
435 | ||
436 | ||
440 | self._command_regex = re.compile(r'^(sudo nmap|nmap|\.\/nmap)\s+.*?') | |
441 | self._use_temp_file = True | |
442 | self._temp_file_extension = "xml" | |
437 | 443 | self.xml_arg_re = re.compile(r"^.*(-oX\s*[^\s]+).*$") |
438 | 444 | self.addSetting("Scan Technique", str, "-sS") |
439 | 445 | |
506 | 512 | description=srvname) |
507 | 513 | |
508 | 514 | for v in port.vulns: |
509 | severity = 0 | |
515 | severity = "info" | |
510 | 516 | desc = v.desc |
511 | 517 | refs = v.refs |
512 | 518 | |
513 | if re.search(r"VULNERABLE", desc): | |
519 | if re.search(r"(?<!NOT )VULNERABLE", desc): | |
514 | 520 | severity = "high" |
515 | 521 | if re.search(r"ERROR", desc): |
516 | 522 | severity = "unclassified" |
537 | 543 | severity=severity, |
538 | 544 | external_id=v.name) |
539 | 545 | del parser |
540 | return True | |
541 | 546 | |
542 | 547 | def processCommandString(self, username, current_path, command_string): |
543 | 548 | """ |
544 | 549 | Adds the -oX parameter to get xml output to the command string that the |
545 | 550 | user has set. |
546 | 551 | """ |
547 | ||
548 | self._output_file_path = os.path.join( | |
549 | self.data_path, | |
550 | "%s_%s_output-%s.xml" % ( | |
551 | self.get_ws(), | |
552 | self.id, | |
553 | random.uniform(1, 10)) | |
554 | ) | |
555 | ||
552 | super().processCommandString(username, current_path, command_string) | |
556 | 553 | arg_match = self.xml_arg_re.match(command_string) |
557 | ||
558 | 554 | if arg_match is None: |
559 | 555 | return re.sub(r"(^.*?nmap)", |
560 | 556 | r"\1 -oX %s" % self._output_file_path, |
564 | 560 | r"-oX %s" % self._output_file_path, |
565 | 561 | command_string) |
566 | 562 | |
567 | def setHost(self): | |
568 | pass | |
569 | ||
570 | ||
571 | 563 | def createPlugin(): |
572 | 564 | return NmapPlugin() |
573 | 565 |
4 | 4 | |
5 | 5 | """ |
6 | 6 | import re |
7 | import os | |
8 | 7 | from collections import defaultdict |
9 | ||
10 | 8 | from copy import copy |
11 | 9 | |
12 | 10 | try: |
22 | 20 | |
23 | 21 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
24 | 22 | |
25 | current_path = os.path.abspath(os.getcwd()) | |
26 | 23 | |
27 | 24 | __author__ = "Francisco Amato" |
28 | 25 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
79 | 76 | """ |
80 | 77 | try: |
81 | 78 | report = tree.find('report') |
82 | results = report.findall('results') | |
83 | if results: | |
84 | nodes = report.findall('results')[0] | |
79 | if report: | |
80 | results = report.findall('results') | |
81 | if results: | |
82 | nodes = report.findall('results')[0] | |
83 | else: | |
84 | nodes = tree.findall('result') | |
85 | 85 | else: |
86 | 86 | nodes = tree.findall('result') |
87 | ||
87 | 88 | for node in nodes: |
88 | 89 | try: |
89 | 90 | yield Item(node, hosts) |
90 | 91 | except Exception as e: |
91 | self.logger.error("Error generating Item from %s [%s]", node.attrib, e) | |
92 | self.logger.error("Error generating Iteem from %s [%s]", node.attrib, e) | |
93 | ||
92 | 94 | except Exception as e: |
93 | 95 | self.logger.error("Tag not found: %s", e) |
94 | 96 | |
185 | 187 | if not self.port: |
186 | 188 | self.service = info[0] |
187 | 189 | else: |
188 | host_details = hosts[self.host].get('details') | |
189 | self.service = self.get_service(port_string, self.port, host_details) | |
190 | if hosts: | |
191 | host_details = hosts[self.host].get('details') | |
192 | self.service = self.get_service(port_string, self.port, host_details) | |
193 | else: | |
194 | self.service = "Not Service" | |
190 | 195 | self.nvt = self.node.findall('nvt')[0] |
191 | 196 | self.node = self.nvt |
192 | 197 | self.id = self.node.get('oid') |
316 | 321 | |
317 | 322 | def __init__(self): |
318 | 323 | super().__init__() |
319 | self.identifier_tag = "report" | |
324 | self.identifier_tag = ["report", "get_results_response"] | |
320 | 325 | self.id = "Openvas" |
321 | 326 | self.name = "Openvas XML Output Plugin" |
322 | 327 | self.plugin_version = "0.3" |
323 | 328 | self.version = "9.0.3" |
324 | 329 | self.framework_version = "1.0.0" |
325 | 330 | self.options = None |
326 | self._current_output = None | |
327 | self.target = None | |
328 | self._command_regex = re.compile( | |
329 | r'^(openvas|sudo openvas|\.\/openvas).*?') | |
330 | ||
331 | 331 | |
332 | 332 | def report_belongs_to(self, **kwargs): |
333 | 333 | if super().report_belongs_to(**kwargs): |
357 | 357 | hostnames=values['hostnames'] |
358 | 358 | ) |
359 | 359 | ids[ip] = h_id |
360 | ||
361 | 360 | for item in parser.items: |
361 | ||
362 | 362 | if item.name is not None: |
363 | 363 | ref = [] |
364 | 364 | if item.cve: |
365 | 365 | cves = item.cve.split(',') |
366 | 366 | for cve in cves: |
367 | ref.append(cve.encode("utf-8").strip()) | |
367 | ref.append(cve.strip()) | |
368 | 368 | if item.bid: |
369 | 369 | bids = item.bid.split(',') |
370 | 370 | for bid in bids: |
371 | ref.append("BID-%s" % bid.encode("utf-8").strip() ) | |
371 | ref.append("BID-%s" % bid.strip()) | |
372 | 372 | if item.xref: |
373 | ref.append(item.xref.encode("utf-8")) | |
373 | ref.append(item.xref) | |
374 | 374 | if item.tags and item.cvss_vector: |
375 | ref.append(item.cvss_vector.encode("utf-8")) | |
375 | ref.append(item.cvss_vector) | |
376 | 376 | |
377 | 377 | if item.subnet in ids: |
378 | 378 | h_id = ids[item.host] |
443 | 443 | else: |
444 | 444 | return False |
445 | 445 | |
446 | def processCommandString(self, username, current_path, command_string): | |
447 | return None | |
448 | 446 | |
449 | 447 | def setHost(self): |
450 | 448 | pass |
26 | 26 | self.name = "pasteAnalyzer JSON Output Plugin" |
27 | 27 | self.plugin_version = "1.0.0" |
28 | 28 | self.command_string = "" |
29 | self.current_path = "" | |
30 | 29 | self._command_regex = re.compile( |
31 | r'^(pasteAnalyzer|python pasteAnalyzer.py|\./pasteAnalyzer.py|sudo python pasteAnalyzer.py|sudo \./pasteAnalyzer.py).*?') | |
30 | r'^(pasteAnalyzer|python pasteAnalyzer.py|\./pasteAnalyzer.py|sudo python pasteAnalyzer.py|sudo \./pasteAnalyzer.py)\s+.*?') | |
32 | 31 | |
33 | def parseOutputString(self, output, debug=False): | |
34 | ||
35 | print("[*]Parsing Output...") | |
36 | ||
32 | def parseOutputString(self, output): | |
37 | 33 | # Generating file name with full path. |
38 | 34 | indexStart = self.command_string.find("-j") + 3 |
39 | ||
40 | 35 | fileJson = self.command_string[ |
41 | 36 | indexStart:self.command_string.find(" ", indexStart)] |
42 | ||
43 | fileJson = self.current_path + "/" + fileJson | |
44 | ||
37 | fileJson = self._current_path + "/" + fileJson | |
45 | 38 | try: |
46 | 39 | with open(fileJson, "r") as fileJ: |
47 | 40 | results = json.loads(fileJ.read()) |
48 | ||
49 | 41 | except Exception as e: |
50 | print("\n[!]Exception opening file\n" + str(e)) | |
51 | 42 | return |
52 | ||
53 | 43 | if results == []: |
54 | 44 | return |
55 | ||
56 | print("[*]Results loaded...") | |
57 | ||
58 | 45 | # Configuration initial. |
59 | 46 | hostId = self.createAndAddHost("pasteAnalyzer") |
60 | 47 | interfaceId = self.createAndAddInterface(hostId, "Results") |
65 | 52 | "TcpHTTP", |
66 | 53 | ['80'] |
67 | 54 | ) |
68 | print("[*]Initial Configuration ready....") | |
69 | 55 | |
70 | 56 | # Loading results. |
71 | 57 | for i in range(0, len(results), 2): |
83 | 69 | else: |
84 | 70 | for element2 in element: |
85 | 71 | description += "\n" + element2 |
86 | ||
87 | 72 | self.createAndAddVulnWebToService( |
88 | 73 | hostId, |
89 | 74 | serviceId, |
91 | 76 | description |
92 | 77 | ) |
93 | 78 | |
94 | print("[*]Parse finished, API faraday called...") | |
95 | ||
96 | 79 | def processCommandString(self, username, current_path, command_string): |
97 | ||
98 | print("[*]pasteAnalyzer Plugin running...") | |
80 | super().processCommandString(username, current_path, command_string) | |
99 | 81 | |
100 | 82 | if command_string.find("-j") < 0: |
101 | 83 | command_string += " -j JSON_OUTPUT " |
102 | 84 | |
103 | 85 | self.command_string = command_string |
104 | self.current_path = current_path | |
105 | 86 | |
106 | 87 | return command_string |
107 | 88 |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | 5 | import re |
6 | import socket | |
7 | 6 | from os import path |
8 | from faraday_plugins.plugins.plugin import PluginBase | |
9 | 7 | from urllib.parse import urlparse |
10 | 8 | |
11 | 9 | __author__ = "Andres Tarantini" |
16 | 14 | __maintainer__ = "Andres Tarantini" |
17 | 15 | __email__ = "[email protected]" |
18 | 16 | __status__ = "Development" |
17 | ||
18 | from faraday_plugins.plugins.plugin import PluginBase | |
19 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
19 | 20 | |
20 | 21 | |
21 | 22 | class PeepingTomPlugin(PluginBase): |
30 | 31 | self.plugin_version = "0.0.1" |
31 | 32 | self.version = "02.19.15" |
32 | 33 | self._command_regex = re.compile( |
33 | r'^(python peepingtom.py|\./peepingtom.py).*?') | |
34 | r'^(python peepingtom.py|\./peepingtom.py)\s+.*?') | |
34 | 35 | self._path = None |
35 | 36 | |
36 | 37 | def parseOutputString(self, output): |
47 | 48 | for url in re.findall(r'href=[\'"]?([^\'" >]+)', html): |
48 | 49 | if "://" in url: |
49 | 50 | url_parsed = urlparse(url) |
50 | address = socket.gethostbyname(url_parsed.netloc) | |
51 | address = resolve_hostname(url_parsed.netloc) | |
51 | 52 | host = self.createAndAddHost(address) |
52 | 53 | iface = self.createAndAddInterface( |
53 | 54 | host, address, ipv4_address=address) |
67 | 68 | return True |
68 | 69 | |
69 | 70 | def processCommandString(self, username, current_path, command_string): |
71 | super().processCommandString(username, current_path, command_string) | |
70 | 72 | self._path = current_path |
71 | 73 | |
72 | 74 |
28 | 28 | self.name = "Ping" |
29 | 29 | self.plugin_version = "0.0.1" |
30 | 30 | self.version = "1.0.0" |
31 | self._command_regex = re.compile( | |
32 | r'^(sudo ping|ping|sudo ping6|ping6).*?') | |
31 | self._command_regex = re.compile(r'^(sudo ping|ping|sudo ping6|ping6)\s+.*?') | |
33 | 32 | |
34 | 33 | def parseOutputString(self, output, debug=False): |
35 | 34 | |
55 | 54 | else: |
56 | 55 | return False |
57 | 56 | |
58 | def processCommandString(self, username, current_path, command_string): | |
59 | """ | |
60 | """ | |
61 | return None | |
62 | 57 | |
63 | 58 | |
64 | 59 | def createPlugin(): |
33 | 33 | self.options = None |
34 | 34 | self._current_output = None |
35 | 35 | self._command_regex = re.compile( |
36 | r'^(sudo propecia|\.\/propecia|propecia).*?') | |
36 | r'^(sudo propecia|\.\/propecia|propecia)\s+.*?') | |
37 | 37 | self._host_ip = None |
38 | 38 | self._port = "23" |
39 | 39 | |
62 | 62 | return True |
63 | 63 | |
64 | 64 | def processCommandString(self, username, current_path, command_string): |
65 | """ | |
66 | """ | |
65 | super().processCommandString(username, current_path, command_string) | |
67 | 66 | count_args = command_string.split() |
68 | 67 | |
69 | 68 | if count_args.__len__() == 3: |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | 5 | import re |
6 | import os | |
7 | import logging | |
8 | 6 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
9 | 7 | |
10 | ||
11 | try: | |
12 | import xml.etree.cElementTree as ET | |
13 | import xml.etree.ElementTree as ET_ORIG | |
14 | ETREE_VERSION = ET_ORIG.VERSION | |
15 | except ImportError: | |
16 | import xml.etree.ElementTree as ET | |
17 | ETREE_VERSION = ET.VERSION | |
8 | import xml.etree.ElementTree as ET | |
9 | ETREE_VERSION = ET.VERSION | |
18 | 10 | |
19 | 11 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split('.')] |
20 | 12 | |
21 | logger = logging.getLogger(__name__) | |
22 | ||
23 | current_path = os.path.abspath(os.getcwd()) | |
24 | 13 | |
25 | 14 | __author__ = 'Francisco Amato' |
26 | 15 | __copyright__ = 'Copyright (c) 2013, Infobyte LLC' |
32 | 21 | __status__ = 'Development' |
33 | 22 | |
34 | 23 | |
35 | def cleaner_unicode(string): | |
36 | if string is not None: | |
37 | return string.encode('ascii', errors='backslashreplace') | |
38 | else: | |
39 | return string | |
40 | ||
41 | ||
42 | 24 | def cleaner_results(string): |
43 | ||
44 | 25 | try: |
45 | 26 | result = string.replace('<P>', '').replace('<UL>', ''). \ |
46 | 27 | replace('<LI>', '').replace('<BR>', ''). \ |
47 | 28 | replace('<A HREF="', '').replace('</A>', ' '). \ |
48 | 29 | replace('" TARGET="_blank">', ' ').replace('"', '"') |
49 | 30 | return result |
50 | ||
51 | 31 | except: |
52 | 32 | return '' |
53 | 33 | |
101 | 81 | type_report = None |
102 | 82 | |
103 | 83 | except SyntaxError as err: |
104 | logger.error('SyntaxError: %s.' % (err)) | |
105 | 84 | return None, None |
106 | 85 | |
107 | 86 | return tree, type_report |
136 | 115 | self.vulns = self.getResults(tree) |
137 | 116 | |
138 | 117 | def getResults(self, tree): |
139 | ||
140 | 118 | glossary = tree.find('GLOSSARY/VULN_DETAILS_LIST') |
141 | ||
142 | 119 | for self.issue in self.node.find('VULN_INFO_LIST'): |
143 | 120 | yield ResultsAssetReport(self.issue, glossary) |
144 | 121 | |
179 | 156 | |
180 | 157 | # GLOSSARY TAG |
181 | 158 | self.glossary = glossary |
182 | self.severity = self.severity_dict.get( | |
183 | self.get_text_from_glossary('SEVERITY'), 'info') | |
159 | self.severity = self.severity_dict.get(self.get_text_from_glossary('SEVERITY'), 'info') | |
184 | 160 | self.title = self.get_text_from_glossary('TITLE') |
185 | 161 | self.cvss = self.get_text_from_glossary('CVSS_SCORE/CVSS_BASE') |
186 | 162 | self.pci = self.get_text_from_glossary('PCI_FLAG') |
206 | 182 | self.ref.append(cve_id) |
207 | 183 | |
208 | 184 | if self.cvss: |
209 | self.ref.append('CVSS SCORE: ' + self.cvss) | |
185 | self.ref.append('CVSS SCORE: {}'.format(self.cvss)) | |
210 | 186 | |
211 | 187 | if self.pci: |
212 | self.ref.append('PCI: ' + self.pci) | |
188 | self.ref.append('PCI: {}'.format(self.pci)) | |
213 | 189 | |
214 | 190 | def get_text_from_glossary(self, tag): |
215 | 191 | """ |
220 | 196 | """ |
221 | 197 | |
222 | 198 | for vuln_detail in self.glossary: |
223 | ||
224 | 199 | id_act = vuln_detail.get('id').strip('qid_') |
225 | 200 | if id_act == self.name: |
226 | ||
227 | 201 | text = vuln_detail.find(tag) |
228 | 202 | if text is not None: |
229 | return cleaner_unicode(text.text) | |
203 | return text.text | |
230 | 204 | else: |
231 | 205 | return None |
232 | 206 | |
238 | 212 | """ |
239 | 213 | sub_node = node.find(subnode_xpath_expr) |
240 | 214 | if sub_node is not None: |
241 | return cleaner_unicode(sub_node.text) | |
242 | ||
215 | return sub_node.text | |
243 | 216 | return None |
244 | 217 | |
245 | 218 | |
352 | 325 | """ |
353 | 326 | sub_node = self.node.find(subnode_xpath_expr) |
354 | 327 | if sub_node is not None: |
355 | return cleaner_results(cleaner_unicode(sub_node.text)) | |
356 | ||
328 | return sub_node.text | |
357 | 329 | return None |
358 | 330 | |
359 | 331 | |
371 | 343 | self.version = 'Qualysguard 8.17.1.0.2' |
372 | 344 | self.framework_version = '1.0.0' |
373 | 345 | self.options = None |
374 | self._current_output = None | |
375 | self._command_regex = re.compile( | |
376 | r'^(sudo qualysguard|\.\/qualysguard).*?') | |
377 | 346 | self.open_options = {"mode": "r", "encoding": "utf-8"} |
378 | 347 | |
379 | 348 | def parseOutputString(self, output, debug=False): |
380 | 349 | |
381 | 350 | parser = QualysguardXmlParser(output) |
351 | ||
382 | 352 | |
383 | 353 | for item in parser.items: |
384 | 354 | h_id = self.createAndAddHost( |
401 | 371 | web = False |
402 | 372 | |
403 | 373 | try: |
404 | port = v.port.decode("utf-8") | |
405 | name = v.name.decode("utf-8") | |
374 | port = v.port | |
375 | name = v.name | |
406 | 376 | except (UnicodeDecodeError, AttributeError): |
407 | 377 | port = v.port |
408 | 378 | name = v.name |
443 | 413 | |
444 | 414 | del parser |
445 | 415 | |
446 | def processCommandString(self, username, current_path, command_string): | |
447 | return None | |
448 | 416 | |
449 | 417 | def setHost(self): |
450 | 418 | pass |
454 | 422 | return QualysguardPlugin() |
455 | 423 | |
456 | 424 | |
457 | if __name__ == "__main__": | |
458 | import sys | |
459 | import os | |
460 | if len(sys.argv) == 2: | |
461 | report_file = sys.argv[1] | |
462 | if os.path.isfile(report_file): | |
463 | plugin = createPlugin() | |
464 | plugin.processReport(report_file) | |
465 | print(plugin.get_json()) | |
466 | else: | |
467 | print(f"Report not found: {report_file}") | |
468 | else: | |
469 | print(f"USAGE {sys.argv[0]} REPORT_FILE") | |
470 | # I'm Py3 |
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 | #!/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 | import re | |
8 | from urllib.parse import urlparse | |
9 | ||
10 | from dateutil.parser import parse | |
11 | ||
12 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
13 | ||
14 | try: | |
15 | import xml.etree.cElementTree as ET | |
16 | except ImportError: | |
17 | import xml.etree.ElementTree as ET | |
18 | ||
19 | __author__ = 'Blas Moyano' | |
20 | __copyright__ = 'Copyright 2020, Faraday Project' | |
21 | __credits__ = ['Blas Moyano'] | |
22 | __license__ = '' | |
23 | __version__ = '1.0.0' | |
24 | __status__ = 'Development' | |
25 | ||
26 | ||
27 | class QualysWebappParser: | |
28 | def __init__(self, xml_output): | |
29 | self.tree = self.parse_xml(xml_output) | |
30 | if self.tree: | |
31 | self.info_results = self.get_results_vul(self.tree.find('RESULTS')) | |
32 | self.info_glossary = self.get_glossary_qid(self.tree.find('GLOSSARY')) | |
33 | self.info_appendix = self.get_appendix(self.tree.find('APPENDIX')) | |
34 | else: | |
35 | self.tree = None | |
36 | ||
37 | def parse_xml(self, xml_output): | |
38 | try: | |
39 | tree = ET.fromstring(xml_output) | |
40 | except SyntaxError as err: | |
41 | print('SyntaxError In xml: %s. %s' % (err, xml_output)) | |
42 | return None | |
43 | return tree | |
44 | ||
45 | def get_appendix(self, tree): | |
46 | for self.appendix_tags in tree: | |
47 | yield Appendix(self.appendix_tags) | |
48 | ||
49 | def get_glossary_qid(self, tree): | |
50 | for self.glossary_tags in tree.find('QID_LIST'): | |
51 | yield Glossary(self.glossary_tags) | |
52 | ||
53 | def get_results_vul(self, tree): | |
54 | for self.results_tags in tree.find('VULNERABILITY_LIST'): | |
55 | yield Results(self.results_tags) | |
56 | ||
57 | class Appendix(): | |
58 | def __init__(self, appendix_tags): | |
59 | if appendix_tags.tag == 'SCAN_LIST': | |
60 | self.lista_scan = self.get_scan(appendix_tags.find('SCAN')) | |
61 | elif appendix_tags.tag == 'WEBAPP': | |
62 | self.lista_webapp = self.get_webapp(appendix_tags) | |
63 | ||
64 | def get_scan(self, appendix_tags): | |
65 | self.result_scan = {} | |
66 | for scan in appendix_tags: | |
67 | self.result_scan[scan.tag] = scan.text | |
68 | return self.result_scan | |
69 | ||
70 | def get_webapp(self, appendix_tags): | |
71 | self.result_webapp = {} | |
72 | for webapp in appendix_tags: | |
73 | self.result_webapp[webapp.tag] = webapp.text | |
74 | return self.result_webapp | |
75 | ||
76 | ||
77 | class Glossary(): | |
78 | def __init__(self, glossary_tags): | |
79 | self.lista_qid = self.get_qid_list(glossary_tags) | |
80 | ||
81 | ||
82 | def get_qid_list(self, qid_list_tags): | |
83 | self.dict_result_qid = {} | |
84 | for qid in qid_list_tags: | |
85 | self.dict_result_qid[qid.tag] = qid.text | |
86 | return self.dict_result_qid | |
87 | ||
88 | ||
89 | class Results(): | |
90 | def __init__(self, glossary_tags): | |
91 | self.lista_vul = self.get_qid_list(glossary_tags) | |
92 | ||
93 | def get_qid_list(self, vul_list_tags): | |
94 | self.dict_result_vul = {} | |
95 | for vul in vul_list_tags: | |
96 | self.dict_result_vul[vul.tag] = vul.text | |
97 | return self.dict_result_vul | |
98 | ||
99 | ||
100 | class QualysWebappPlugin(PluginXMLFormat): | |
101 | def __init__(self): | |
102 | super().__init__() | |
103 | self.identifier_tag = ["WAS_SCAN_REPORT"] | |
104 | self.id = 'QualysWebapp' | |
105 | self.name = 'QualysWebapp XML Output Plugin' | |
106 | self.plugin_version = '1.0.0' | |
107 | self.version = '1.0.0' | |
108 | self.framework_version = '1.0.0' | |
109 | self.options = None | |
110 | self.protocol = None | |
111 | self.port = '80' | |
112 | self.address = None | |
113 | ||
114 | def parseOutputString(self, output): | |
115 | hostnames = [] | |
116 | ||
117 | parser = QualysWebappParser(output) | |
118 | ||
119 | if not parser.info_appendix: | |
120 | return | |
121 | ||
122 | self.scan_list_result = [] | |
123 | for host_create in parser.info_appendix: | |
124 | self.scan_list_result.append(host_create) | |
125 | ||
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] | |
133 | ||
134 | glossary = [] | |
135 | for glossary_qid in parser.info_glossary: | |
136 | glossary.append(glossary_qid.dict_result_qid) | |
137 | ||
138 | for v in parser.info_results: | |
139 | url = urlparse(v.dict_result_vul.get('URL')) | |
140 | ||
141 | host_id = self.createAndAddHost(name=url.netloc, os=os, hostnames=hostnames) | |
142 | ||
143 | vuln_scan_id = v.dict_result_vul.get('QID') | |
144 | ||
145 | # Data in the xml is in different parts, we look into the glossary | |
146 | vuln_data = next((item for item in glossary if item["QID"] == vuln_scan_id), None) | |
147 | vuln_name = vuln_data.get('TITLE') | |
148 | vuln_desc = vuln_data.get('DESCRIPTION') | |
149 | ||
150 | raw_severity = int(vuln_data.get('SEVERITY', 0)) | |
151 | vuln_severity = raw_severity - 1 | |
152 | ||
153 | run_date = parse(v.dict_result_vul.get('FIRST_TIME_DETECTED')) | |
154 | vuln_resolution = vuln_data.get('SOLUTION') | |
155 | ||
156 | vuln_ref = [] | |
157 | if vuln_data.get('CVSS_BASE'): | |
158 | vuln_ref = ["CVSS: {}".format(vuln_data.get('CVSS_BASE'))] | |
159 | ||
160 | vuln_data_add = "ID: {}, DETECTION_ID: {}, CATEGORY: {}, GROUP: {}, URL: {}, IMPACT: {}".format( | |
161 | v.dict_result_vul.get('ID'), v.dict_result_vul.get('DETECTION_ID'), vuln_data.get('CATEGORY'), | |
162 | vuln_data.get('GROUP'), v.dict_result_vul.get('URL'), vuln_data.get('IMPACT')) | |
163 | ||
164 | self.createAndAddVulnToHost(host_id=host_id, name=vuln_name, desc=vuln_desc, ref=vuln_ref, | |
165 | severity=vuln_severity, resolution=vuln_resolution, run_date=run_date, | |
166 | external_id=vuln_scan_id, data=vuln_data_add) | |
167 | ||
168 | ||
169 | def createPlugin(): | |
170 | return QualysWebappPlugin() |
4 | 4 | """ |
5 | 5 | import re |
6 | 6 | import json |
7 | import socket | |
8 | 7 | import logging |
8 | ||
9 | 9 | try: |
10 | 10 | from lxml import etree as ET |
11 | 11 | except ImportError: |
12 | 12 | import xml.etree.ElementTree as ET |
13 | 13 | |
14 | 14 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
15 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
15 | 16 | |
16 | 17 | __author__ = 'Leonardo Lazzaro' |
17 | 18 | __copyright__ = 'Copyright (c) 2017, Infobyte LLC' |
135 | 136 | self.version = '' |
136 | 137 | self.framework_version = '' |
137 | 138 | self.options = None |
138 | self._current_output = None | |
139 | self._command_regex = re.compile( | |
140 | r'records added to') | |
141 | ||
142 | 139 | self.host_mapper = {} |
143 | 140 | |
144 | 141 | def parseOutputString(self, output): |
152 | 149 | self.host_mapper[host['host']] = h_id |
153 | 150 | for vuln in parser.vulns: |
154 | 151 | if vuln['host'] not in list(self.host_mapper.keys()): |
155 | ip = self.resolve_host(vuln['host']) | |
152 | ip = resolve_hostname(vuln['host']) | |
156 | 153 | h_id = self.createAndAddHost( |
157 | 154 | ip, |
158 | 155 | hostnames=[vuln['host']] |
170 | 167 | data=vuln['example'] |
171 | 168 | ) |
172 | 169 | |
173 | def processCommandString(self, username, current_path, command_string): | |
174 | return | |
175 | 170 | |
176 | def resolve_host(self, host): | |
177 | try: | |
178 | return socket.gethostbyname(host) | |
179 | except Exception: | |
180 | pass | |
181 | return host | |
182 | 171 | |
183 | 172 | |
184 | 173 | def createPlugin(): |
4 | 4 | |
5 | 5 | """ |
6 | 6 | import re |
7 | import os | |
8 | 7 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
9 | 8 | |
10 | 9 | |
18 | 17 | |
19 | 18 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
20 | 19 | |
21 | current_path = os.path.abspath(os.getcwd()) | |
22 | 20 | |
23 | 21 | __author__ = "Francisco Amato" |
24 | 22 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
182 | 180 | self.version = "Retina Network 5.19.2.2718" |
183 | 181 | self.framework_version = "1.0.0" |
184 | 182 | self.options = None |
185 | self._current_output = None | |
186 | self._command_regex = re.compile(r'^(sudo retina|\.\/retina).*?') | |
187 | 183 | |
188 | 184 | |
189 | 185 | def parseOutputString(self, output, debug=False): |
211 | 207 | v.protocol.lower(), |
212 | 208 | ports=[str(v.port)], |
213 | 209 | status="open") |
214 | ||
215 | 210 | if v.port in ['80', '443'] or re.search("ssl|http", v.name.lower()): |
216 | 211 | web = True |
217 | 212 | else: |
218 | 213 | web = False |
219 | ||
220 | 214 | if web: |
221 | v_id = self.createAndAddVulnWebToService(h_id, s_id, v.name.encode( | |
222 | "utf-8"), ref=v.ref, website=hostname, severity=v.severity, resolution=v.solution.encode("utf-8"), desc=v.desc.encode("utf-8")) | |
215 | v_id = self.createAndAddVulnWebToService(h_id, s_id, v.name, ref=v.ref, | |
216 | website=hostname, severity=v.severity, | |
217 | resolution=v.solution, desc=v.desc) | |
223 | 218 | else: |
224 | v_id = self.createAndAddVulnToService(h_id, s_id, v.name.encode( | |
225 | "utf-8"), ref=v.ref, severity=v.severity, resolution=v.solution.encode("utf-8"), desc=v.desc.encode("utf-8")) | |
219 | v_id = self.createAndAddVulnToService(h_id, s_id, v.name, ref=v.ref, | |
220 | severity=v.severity, resolution=v.solution, | |
221 | desc=v.desc) | |
226 | 222 | else: |
227 | 223 | for v in vulns: |
228 | v_id = self.createAndAddVulnToHost(h_id, v.name.encode( | |
229 | "utf-8"), ref=v.ref, severity=v.severity, resolution=v.solution.encode("utf-8"), desc=v.desc.encode("utf-8")) | |
224 | v_id = self.createAndAddVulnToHost(h_id, v.name, ref=v.ref, severity=v.severity, | |
225 | resolution=v.solution, desc=v.desc) | |
230 | 226 | del parser |
231 | 227 | |
232 | def processCommandString(self, username, current_path, command_string): | |
233 | return None | |
234 | 228 | |
235 | 229 | def setHost(self): |
236 | 230 | pass |
5 | 5 | """ |
6 | 6 | from faraday_plugins.plugins.plugin import PluginBase |
7 | 7 | import re |
8 | import os | |
9 | 8 | |
10 | 9 | |
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | 10 | |
14 | 11 | __author__ = "Francisco Amato" |
15 | 12 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
58 | 55 | self.plugin_version = "0.0.1" |
59 | 56 | self.version = "0.7.6" |
60 | 57 | self.options = None |
61 | self._current_output = None | |
62 | self._current_path = None | |
63 | self._command_regex = re.compile( | |
64 | r'^(sudo \.\/reverseraider|\.\/reverseraider).*?') | |
58 | self._command_regex = re.compile(r'^(sudo \.\/reverseraider|\.\/reverseraider)\s+.*?') | |
65 | 59 | self._completition = { |
66 | 60 | "": "reverseraider -d domain | -r range [options]", |
67 | 61 | "-r": "range of ipv4 or ipv6 addresses, for reverse scanning", |
74 | 68 | "-R": "don't set the recursion bit on queries", |
75 | 69 | } |
76 | 70 | |
77 | global current_path | |
78 | 71 | |
79 | def canParseCommandString(self, current_input): | |
80 | if self._command_regex.match(current_input.strip()): | |
81 | return True | |
82 | else: | |
83 | return False | |
84 | ||
85 | def parseOutputString(self, output, debug=False): | |
86 | """ | |
87 | This method will discard the output the shell sends, it will read it from | |
88 | the xml where it expects it to be present. | |
89 | ||
90 | NOTE: if 'debug' is true then it is being run from a test case and the | |
91 | output being sent is valid. | |
92 | """ | |
93 | ||
94 | if debug: | |
95 | parser = ReverseraiderParser(output) | |
96 | else: | |
97 | ||
98 | parser = ReverseraiderParser(output) | |
99 | ||
100 | for item in parser.items: | |
101 | h_id = self.createAndAddHost(item['ip']) | |
102 | i_id = self.createAndAddInterface( | |
103 | h_id, item['ip'], ipv4_address=item['ip']) | |
104 | ||
72 | def parseOutputString(self, output): | |
73 | parser = ReverseraiderParser(output) | |
74 | for item in parser.items: | |
75 | h_id = self.createAndAddHost(item['ip']) | |
76 | i_id = self.createAndAddInterface(h_id, item['ip'], ipv4_address=item['ip']) | |
105 | 77 | del parser |
106 | 78 | |
107 | def processCommandString(self, username, current_path, command_string): | |
108 | """ | |
109 | """ | |
110 | return None | |
111 | 79 | |
112 | 80 | |
113 | 81 | def createPlugin(): |
6 | 6 | import re |
7 | 7 | import os |
8 | 8 | import json |
9 | import socket | |
10 | 9 | import random |
10 | import shutil | |
11 | import tempfile | |
12 | ||
11 | 13 | from faraday_plugins.plugins.plugin import PluginBase |
12 | ||
13 | current_path = os.path.abspath(os.getcwd()) | |
14 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
15 | ||
14 | 16 | |
15 | 17 | __author__ = "Nicolas Rodriguez" |
16 | 18 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
111 | 113 | def __init__(self): |
112 | 114 | super().__init__() |
113 | 115 | self.id = "Skipfish" |
114 | self.name = "Skipfish XML Output Plugin" | |
116 | self.name = "Skipfish Output Plugin" | |
115 | 117 | self.plugin_version = "0.0.2" |
116 | 118 | self.version = "2.1.5" |
117 | 119 | self.options = None |
118 | self._current_output = None | |
119 | 120 | self.parent = None |
120 | 121 | self._command_regex = re.compile( |
121 | r'^(sudo skipfish|skipfish|sudo skipfish\.pl|skipfish\.pl|perl skipfish\.pl|\.\/skipfish\.pl|\.\/skipfish).*?') | |
122 | global current_path | |
123 | ||
124 | def parseOutputString(self, output, debug=False): | |
122 | r'^(sudo skipfish|skipfish|sudo skipfish\.pl|skipfish\.pl|perl skipfish\.pl|\.\/skipfish\.pl|\.\/skipfish)\s+.*?') | |
123 | ||
124 | def _parse_filename(self, filename): | |
125 | self.parseOutputString(filename) | |
126 | if self._delete_temp_file: | |
127 | try: | |
128 | if os.path.isfile(filename): | |
129 | os.remove(filename) | |
130 | elif os.path.isdir(filename): | |
131 | shutil.rmtree(filename) | |
132 | except Exception as e: | |
133 | self.logger.error("Error on delete file: (%s) [%s]", filename, e) | |
134 | ||
135 | def parseOutputString(self, output): | |
125 | 136 | """ |
126 | 137 | This method will discard the output the shell sends, it will read it |
127 | 138 | from the xml where it expects it to be present. |
130 | 141 | output being sent is valid. |
131 | 142 | """ |
132 | 143 | |
133 | if not os.path.exists(self._output_path): | |
144 | if not os.path.isdir(self._output_file_path): | |
134 | 145 | return False |
135 | 146 | |
136 | p = SkipfishParser(self._output_path) | |
147 | p = SkipfishParser(self._output_file_path) | |
137 | 148 | |
138 | 149 | hostc = {} |
139 | 150 | port = 80 |
152 | 163 | else: |
153 | 164 | port = 443 if protocol == "https" else 80 |
154 | 165 | |
155 | ip = self.resolve(host) | |
166 | ip = resolve_hostname(host) | |
156 | 167 | |
157 | 168 | h_id = self.createAndAddHost(ip) |
158 | 169 | i_id = self.createAndAddInterface( |
198 | 209 | path=sample["url"], |
199 | 210 | severity=issue["severity"]) |
200 | 211 | |
201 | def resolve(self, host): | |
202 | try: | |
203 | return socket.gethostbyname(host) | |
204 | except Exception: | |
205 | pass | |
206 | return host | |
207 | 212 | |
208 | 213 | xml_arg_re = re.compile(r"^.*(-o\s*[^\s]+).*$") |
209 | 214 | |
212 | 217 | Adds the -o parameter to get report of the command string that the |
213 | 218 | user has set. |
214 | 219 | """ |
220 | super().processCommandString(username, current_path, command_string) | |
215 | 221 | arg_match = self.xml_arg_re.match(command_string) |
216 | ||
217 | self._output_path = os.path.join( | |
218 | self.data_path, | |
219 | "skipfish_output-%s" % random.uniform(1, 10)) | |
220 | ||
222 | self._output_file_path = os.path.join(tempfile.gettempdir(), "faraday_plugin_skipfish_%d" % random.randint(1, 999999)) | |
223 | self._delete_temp_file = True | |
221 | 224 | if arg_match is None: |
222 | return re.sub( | |
223 | r"(^.*?skipfish)", | |
224 | r"\1 -o %s" % self._output_path, | |
225 | command_string, | |
226 | 1) | |
225 | return re.sub(r"(^.*?skipfish)", r"\1 -o %s" % self._output_file_path, command_string, 1) | |
227 | 226 | else: |
228 | return re.sub( | |
229 | arg_match.group(1), | |
230 | r"-o %s" % self._output_path, | |
231 | command_string, | |
232 | 1) | |
227 | return re.sub(arg_match.group(1), r"-o %s" % self._output_file_path, command_string, 1) | |
233 | 228 | |
234 | 229 | def setHost(self): |
235 | 230 | pass |
28 | 28 | self.plugin_version = "0.0.1" |
29 | 29 | self.version = "1.0.0" |
30 | 30 | self._command_regex = re.compile( |
31 | r'^(python sshdefaultscan.py|\./sshdefaultscan.py).*?') | |
31 | r'^(python sshdefaultscan.py|\./sshdefaultscan.py)\s+.*?') | |
32 | 32 | self._completition = {"--fast": "Fast scan mode"} |
33 | 33 | |
34 | def parseOutputString(self, output, debug=False): | |
35 | for line in [l.strip() for l in output.split("\n")]: | |
34 | def parseOutputString(self, output): | |
35 | for line in [line.strip() for line in output.split("\n")]: | |
36 | 36 | output_rexeg_match = re.match( |
37 | 37 | r".*:.*@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", line) |
38 | 38 | if output_rexeg_match: |
57 | 57 | severity=3 |
58 | 58 | ) |
59 | 59 | |
60 | return True | |
61 | 60 | |
62 | 61 | def processCommandString(self, username, current_path, command_string): |
62 | super().processCommandString(username, current_path, command_string) | |
63 | 63 | if "--batch" not in command_string: |
64 | 64 | return "{command} --batch --batch-template {template}".format( |
65 | 65 | command=command_string, |
66 | 66 | template="{username}:{password}@{host}" |
67 | 67 | ) |
68 | ||
69 | return None | |
68 | else: | |
69 | return None | |
70 | 70 | |
71 | 71 | |
72 | 72 | def createPlugin(): |
0 | 0 | import re |
1 | import os | |
2 | import random | |
3 | 1 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
4 | 2 | |
5 | 3 | try: |
96 | 94 | self.framework_version = "1.0.0" |
97 | 95 | self.options = None |
98 | 96 | self._current_output = None |
99 | self._command_regex = re.compile(r'^(sudo sslyze|sslyze|\.\/sslyze).*?') | |
97 | self._command_regex = re.compile(r'^(sudo sslyze|sslyze|\.\/sslyze)\s+.*?') | |
100 | 98 | self.xml_arg_re = re.compile(r"^.*(--xml_output\s*[^\s]+).*$") |
99 | self._use_temp_file = True | |
100 | self._temp_file_extension = "xml" | |
101 | 101 | |
102 | 102 | def report_belongs_to(self, **kwargs): |
103 | 103 | if super().report_belongs_to(**kwargs): |
170 | 170 | severity="medium") |
171 | 171 | |
172 | 172 | def processCommandString(self, username, current_path, command_string): |
173 | self._output_file_path = os.path.join( | |
174 | self.data_path, | |
175 | "%s_%s_output-%s.xml" % ( | |
176 | self.get_ws(), | |
177 | self.id, | |
178 | random.uniform(1, 10)) | |
179 | ) | |
180 | ||
173 | super().processCommandString(username, current_path, command_string) | |
181 | 174 | arg_match = self.xml_arg_re.match(command_string) |
182 | ||
183 | 175 | if arg_match is None: |
184 | 176 | return re.sub(r"(^.*?sslyze)", |
185 | 177 | r"\1 --xml_out %s" % self._output_file_path, |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | import re | |
6 | 7 | from faraday_plugins.plugins.plugin import PluginBase |
7 | import re | |
8 | import os | |
9 | import socket | |
10 | ||
11 | current_path = os.path.abspath(os.getcwd()) | |
8 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
12 | 9 | |
13 | 10 | __author__ = "Facundo de Guzmán, Esteban Guillardoy" |
14 | 11 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
35 | 32 | self.framework_version = "1.0.0" |
36 | 33 | self.options = None |
37 | 34 | self._current_output = None |
38 | self._command_regex = re.compile(r'^telnet.*?') | |
35 | self._command_regex = re.compile(r'^telnet\s+.*?') | |
39 | 36 | self._host_ip = None |
40 | 37 | self._host = [] |
41 | 38 | self._port = "23" |
56 | 53 | "-n": "-n <tracefile> Opens tracefile for recording trace information. See the set tracefile command below.", |
57 | 54 | } |
58 | 55 | |
59 | global current_path | |
60 | 56 | |
61 | def resolve(self, host): | |
62 | try: | |
63 | return socket.gethostbyname(host) | |
64 | except: | |
65 | pass | |
66 | return host | |
67 | 57 | |
68 | 58 | def parseOutputString(self, output, debug=False): |
69 | 59 | |
70 | 60 | host_info = re.search(r"Connected to (.+)\.", output) |
71 | 61 | |
72 | 62 | hostname = host_info.group(1) |
73 | ip_address = self.resolve(hostname) | |
63 | ip_address = resolve_hostname(hostname) | |
74 | 64 | |
75 | 65 | if host_info is not None: |
76 | 66 | h_id = self.createAndAddHost(ip_address) |
83 | 73 | return True |
84 | 74 | |
85 | 75 | def processCommandString(self, username, current_path, command_string): |
86 | ||
76 | super().processCommandString(username, current_path, command_string) | |
87 | 77 | count_args = command_string.split() |
88 | ||
89 | 78 | c = count_args.__len__() |
90 | 79 | self._port = "23" |
91 | 80 | if re.search(r"[\d]+", count_args[c - 1]): |
5 | 5 | """ |
6 | 6 | from faraday_plugins.plugins.plugin import PluginBase |
7 | 7 | import re |
8 | import os | |
9 | 8 | |
10 | 9 | |
11 | ||
12 | current_path = os.path.abspath(os.getcwd()) | |
13 | 10 | |
14 | 11 | __author__ = "Francisco Amato" |
15 | 12 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
85 | 82 | self._current_output = None |
86 | 83 | self._current_path = None |
87 | 84 | self._command_regex = re.compile( |
88 | r'^(theharvester|sudo theharvester|sudo theHarvester\.py|theHarvester\.py|python theHarvester\.py|\.\/theHarvester\.py).*?') | |
85 | r'^(sudo theHarvester\.py|theHarvester\.py|python theHarvester\.py|\.\/theHarvester\.py)\s+.*?') | |
89 | 86 | self._completition = { |
90 | 87 | "": "Examples:./theharvester.py -d microsoft.com -l 500 -b google", |
91 | 88 | "-d": "Domain to search or company name", |
101 | 98 | "-h": "use SHODAN database to query discovered hosts. google 100 to 100, and pgp doesn't use this option)", |
102 | 99 | } |
103 | 100 | |
104 | global current_path | |
105 | 101 | |
106 | 102 | def parseOutputString(self, output, debug=False): |
107 | 103 | """ |
131 | 127 | |
132 | 128 | del parser |
133 | 129 | |
134 | def processCommandString(self, username, current_path, command_string): | |
135 | """ | |
136 | """ | |
137 | return None | |
138 | 130 | |
139 | 131 | |
140 | 132 | def createPlugin(): |
21 | 21 | self.name = "Traceroute" |
22 | 22 | self.plugin_version = "1.0.0" |
23 | 23 | self.command_string = "" |
24 | self._command_regex = re.compile( | |
25 | r'^(traceroute|traceroute6).*?') | |
24 | self._command_regex = re.compile(r'^(traceroute|traceroute6)\s+.*?') | |
26 | 25 | |
27 | 26 | def parseOutputString(self, output, debug=False): |
28 | 27 | |
51 | 50 | print("[*]Parse finished, API faraday called...") |
52 | 51 | |
53 | 52 | def processCommandString(self, username, current_path, command_string): |
54 | ||
55 | print("[*]traceroute Plugin running...") | |
53 | super().processCommandString(username, current_path, command_string) | |
56 | 54 | self.command_string = command_string |
57 | return command_string | |
55 | return None | |
58 | 56 | |
59 | 57 | |
60 | 58 | def createPlugin(): |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | ||
6 | import re | |
7 | from urllib.parse import urlparse | |
7 | 8 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
8 | import re | |
9 | import os | |
10 | import socket | |
11 | import pprint | |
12 | import sys | |
9 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
13 | 10 | |
14 | 11 | try: |
15 | 12 | import xml.etree.cElementTree as ET |
21 | 18 | |
22 | 19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
23 | 20 | |
24 | current_path = os.path.abspath(os.getcwd()) | |
25 | 21 | |
26 | 22 | __author__ = "Francisco Amato" |
27 | 23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
68 | 64 | try: |
69 | 65 | tree = ET.fromstring(xml_output) |
70 | 66 | except SyntaxError as err: |
71 | print("SyntaxError: %s. %s" % (err, xml_output)) | |
72 | 67 | return None |
73 | 68 | |
74 | 69 | return tree |
85 | 80 | scaninfo = tree.findall('scan-info')[0] |
86 | 81 | |
87 | 82 | self.target = scaninfo.get('target') |
88 | host = re.search( | |
89 | "(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))[\:]*([0-9]+)*([/]*($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+)).*?$", self.target) | |
90 | ||
91 | self.protocol = host.group(1) | |
92 | self.host = host.group(4) | |
93 | if self.protocol == 'https': | |
94 | self.port = 443 | |
95 | if host.group(11) is not None: | |
96 | self.port = host.group(11) | |
83 | url_parse = urlparse(self.target) | |
84 | ||
85 | self.protocol = url_parse.scheme | |
86 | self.host = url_parse.netloc | |
87 | self.port = url_parse.port | |
88 | if self.port is None: | |
89 | if self.protocol == 'https': | |
90 | self.port = 443 | |
91 | elif self.protocol == 'http': | |
92 | self.port = 80 | |
97 | 93 | |
98 | 94 | for node in tree.findall('vulnerability'): |
99 | 95 | yield Item(node) |
151 | 147 | self.url = self.url if self.url != 'None' else "/" |
152 | 148 | self.plugin = self.node.get('plugin') |
153 | 149 | self.detail = self.get_text_from_subnode('description') |
150 | if not self.detail: | |
151 | self.detail = self.node.text.strip('\n').strip() | |
154 | 152 | self.resolution = self.get_text_from_subnode('fix-guidance') |
155 | 153 | self.fix_effort = self.get_text_from_subnode('fix-effort') |
156 | 154 | self.longdetail = self.get_text_from_subnode('description') |
220 | 218 | self.options = None |
221 | 219 | self._current_output = None |
222 | 220 | self.target = None |
223 | self._command_regex = re.compile(r'^(w3af|sudo w3af|\.\/w3af).*?') | |
221 | self._command_regex = re.compile(r'^(w3af|sudo w3af|\.\/w3af)\s+.*?') | |
224 | 222 | self._completition = { |
225 | 223 | "": "", |
226 | 224 | "-h": "Display this help message.", |
229 | 227 | def parseOutputString(self, output, debug=False): |
230 | 228 | |
231 | 229 | parser = W3afXmlParser(output) |
232 | ip = self.resolve(parser.host) | |
230 | ip = resolve_hostname(parser.host) | |
233 | 231 | h_id = self.createAndAddHost(ip) |
234 | 232 | i_id = self.createAndAddInterface(h_id, ip, ipv4_address=ip, hostname_resolution=[parser.host]) |
235 | 233 | s_id = self.createAndAddServiceToInterface(h_id, i_id, "http", "tcp", ports=[parser.port], status="open") |
241 | 239 | resolution=item.resolution, ref=item.ref, response=item.resp) |
242 | 240 | del parser |
243 | 241 | |
244 | def resolve(self, host): | |
245 | try: | |
246 | return socket.gethostbyname(host) | |
247 | except: | |
248 | pass | |
249 | return host | |
250 | ||
251 | def processCommandString(self, username, current_path, command_string): | |
252 | return None | |
253 | 242 | |
254 | 243 | def setHost(self): |
255 | 244 | pass |
4 | 4 | |
5 | 5 | """ |
6 | 6 | import re |
7 | import os | |
8 | import socket | |
9 | ||
10 | 7 | from urllib.parse import urlparse |
11 | 8 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
9 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
10 | ||
12 | 11 | try: |
13 | 12 | import xml.etree.cElementTree as ET |
14 | 13 | import xml.etree.ElementTree as ET_ORIG |
19 | 18 | |
20 | 19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
21 | 20 | |
22 | current_path = os.path.abspath(os.getcwd()) | |
23 | 21 | |
24 | 22 | __author__ = "Francisco Amato" |
25 | 23 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
119 | 117 | self.node = item_node |
120 | 118 | self.url = self.get_url(item_node) |
121 | 119 | if self.url.hostname is not None: |
122 | self.ip = socket.gethostbyname(self.url.hostname) | |
120 | self.ip = resolve_hostname(self.url.hostname) | |
123 | 121 | else: |
124 | 122 | self.ip = '0.0.0.0' |
125 | 123 | self.hostname = self.url.hostname |
233 | 231 | self.plugin_version = "0.0.1" |
234 | 232 | self.version = "2.2.1" |
235 | 233 | self.options = None |
236 | self._current_output = None | |
237 | 234 | self.protocol = None |
238 | 235 | self.host = None |
239 | 236 | self.port = "80" |
237 | self._use_temp_file = True | |
238 | self._temp_file_extension = "xml" | |
240 | 239 | self.xml_arg_re = re.compile(r"^.*(-oX\s*[^\s]+).*$") |
241 | 240 | self._command_regex = re.compile( |
242 | r'^(python wapiti|wapiti|sudo wapiti|sudo wapiti\.py|wapiti\.py|python wapiti\.py|\.\/wapiti\.py|wapiti|\.' | |
243 | r'\/wapiti|python wapiti|python \.\/wapiti).*?') | |
241 | r'^(python wapiti|wapiti|sudo wapiti|sudo wapiti\.py|wapiti\.py|python wapiti\.py|\.\/wapiti\.py|wapiti |\.' | |
242 | r'\.\/wapiti|python wapiti|python \.\/wapiti)\s+.*?') | |
244 | 243 | self._completition = { |
245 | 244 | "": "python wapiti.py http://server.com/base/url/ [options]", |
246 | 245 | "-s": "<url> ", |
324 | 323 | Adds the -oX parameter to get xml output to the command string that the |
325 | 324 | user has set. |
326 | 325 | """ |
326 | super().processCommandString(username, current_path, command_string) | |
327 | 327 | host = re.search( |
328 | 328 | "(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]" |
329 | 329 | "{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2" |
339 | 339 | if self.protocol == 'https': |
340 | 340 | self.port = 443 |
341 | 341 | self.logger.debug("host = %s, port = %s",self.host, self.port) |
342 | arg_match = self.xml_arg_re.match(command_string) | |
343 | 342 | return "%s -o %s -f xml \n" % (command_string, self._output_file_path) |
344 | 343 | |
345 | 344 | def setHost(self): |
18 | 18 | |
19 | 19 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
20 | 20 | |
21 | current_path = os.path.abspath(os.getcwd()) | |
22 | 21 | |
23 | 22 | __author__ = "Morgan Lemarechal" |
24 | 23 | __copyright__ = "Copyright 2014, Faraday Project" |
87 | 86 | } |
88 | 87 | |
89 | 88 | self.options = None |
90 | self._current_output = None | |
91 | self.current_path = None | |
92 | self._command_regex = re.compile( | |
93 | r'^(sudo wcscan|wcscan|\.\/wcscan).*?') | |
89 | self._command_regex = re.compile(r'^(sudo wcscan|wcscan|\.\/wcscan)\s+.*?') | |
90 | self._use_temp_file = True | |
91 | self._temp_file_extension = "xml" | |
92 | self.xml_arg_re = re.compile(r"^.*(--xml\s*[^\s]+).*$") | |
94 | 93 | |
95 | 94 | |
96 | def canParseCommandString(self, current_input): | |
97 | if self._command_regex.match(current_input.strip()): | |
98 | return True | |
99 | else: | |
100 | return False | |
101 | ||
102 | def parseOutputString(self, output, debug=False): | |
95 | def parseOutputString(self, output): | |
103 | 96 | """ |
104 | 97 | This method will discard the output the shell sends, it will read it from |
105 | 98 | the xml where it expects it to be present. |
106 | 99 | NOTE: if 'debug' is true then it is being run from a test case and the |
107 | 100 | output being sent is valid. |
108 | 101 | """ |
109 | if debug: | |
110 | parser = WcscanParser(self._output_file_path) | |
111 | else: | |
102 | parser = WcscanParser(output) | |
103 | for file in parser.scaninfo: | |
104 | host = parser.scaninfo[file]['host'] | |
105 | port = parser.scaninfo[file]['port'] | |
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) | |
112 | 115 | |
113 | if not os.path.exists(self._output_file_path): | |
114 | return False | |
115 | parser = WcscanParser(self._output_file_path) | |
116 | s_id = self.createAndAddServiceToInterface( | |
117 | h_id, i_id, "http", protocol="tcp", ports=port) | |
118 | for vuln in parser.result[file]: | |
119 | if parser.scaninfo[file]['type'] == "phpini": | |
120 | vuln_name = f"{parser.scaninfo[file]['file']}: {vuln}" | |
121 | vuln_description = f"{vuln}: {str(parser.result[file][vuln][0])}\n{str(parser.result[file][vuln][1])}" | |
122 | v_id = self.createAndAddVulnToService(h_id, s_id, vuln_name, desc=vuln_description, severity=0) | |
116 | 123 | |
117 | for file in parser.scaninfo: | |
118 | host = parser.scaninfo[file]['host'] | |
119 | port = parser.scaninfo[file]['port'] | |
120 | h_id = self.createAndAddHost(host) | |
121 | 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)): | |
122 | i_id = self.createAndAddInterface(h_id, | |
123 | host, | |
124 | ipv4_address=host) | |
125 | else: | |
126 | i_id = self.createAndAddInterface(h_id, | |
127 | host, | |
128 | ipv6_address=host) | |
124 | if parser.scaninfo[file]['type'] == "webconfig": | |
125 | vuln_name = f"{parser.scaninfo[file]['file']}: {str(parser.result[file][vuln][3])}" | |
126 | vuln_description = f"{str(parser.result[file][vuln][3])} : {str(parser.result[file][vuln][2])} = {str(parser.result[file][vuln][0])}\n{str(parser.result[file][vuln][1])}" | |
127 | v_id = self.createAndAddVulnToService(h_id, s_id, vuln_name, desc=vuln_description, severity=0) | |
129 | 128 | |
130 | s_id = self.createAndAddServiceToInterface( | |
131 | h_id, i_id, "http", protocol="tcp", ports=port) | |
132 | for vuln in parser.result[file]: | |
133 | if parser.scaninfo[file]['type'] == "phpini": | |
134 | v_id = self.createAndAddVulnToService(h_id, s_id, | |
135 | parser.scaninfo[file][ | |
136 | 'file'] + ":" + vuln, | |
137 | desc="{} : {}\n{}".format(vuln, | |
138 | str(parser.result[ | |
139 | file][vuln][0]), | |
140 | str(parser.result[file][vuln][1])), | |
141 | severity=0) | |
142 | 129 | |
143 | if parser.scaninfo[file]['type'] == "webconfig": | |
144 | v_id = self.createAndAddVulnToService(h_id, s_id, | |
145 | parser.scaninfo[file][ | |
146 | 'file'] + ":" + str(parser.result[file][vuln][3]), | |
147 | desc="{} : {} = {}\n{}".format(str(parser.result[file][vuln][3]), | |
148 | str(parser.result[ | |
149 | file][vuln][2]), | |
150 | str(parser.result[ | |
151 | file][vuln][0]), | |
152 | str(parser.result[file][vuln][1])), | |
153 | severity=0) | |
154 | del parser | |
155 | ||
156 | return True | |
157 | ||
158 | xml_arg_re = re.compile(r"^.*(--xml\s*[^\s]+).*$") | |
159 | 130 | |
160 | 131 | def processCommandString(self, username, current_path, command_string): |
161 | 132 | """ |
162 | 133 | Adds the parameter to get output to the command string that the |
163 | 134 | user has set. |
164 | 135 | """ |
165 | ||
136 | super().processCommandString(username, current_path, command_string) | |
166 | 137 | arg_match = self.xml_arg_re.match(command_string) |
167 | 138 | |
168 | 139 | if arg_match is None: |
169 | 140 | return "%s --xml %s" % (command_string, self._output_file_path) |
170 | 141 | else: |
171 | return re.sub(arg_match.group(1), | |
172 | r"-xml %s" % self._output_file_path, | |
173 | command_string) | |
142 | return re.sub(arg_match.group(1), r"-xml %s" % self._output_file_path, command_string) | |
174 | 143 | |
175 | 144 | |
176 | 145 | def createPlugin(): |
64 | 64 | self.items.append(vuln) |
65 | 65 | |
66 | 66 | except SyntaxError as err: |
67 | print("SyntaxError: %s. %s" % (err, self.filepath)) | |
68 | 67 | return None |
69 | 68 | |
70 | 69 | |
82 | 81 | self.options = None |
83 | 82 | self._current_output = None |
84 | 83 | self.host = None |
85 | self._command_regex = re.compile( | |
86 | r'^(sudo webfuzzer|webfuzzer|\.\/webfuzzer).*?') | |
84 | self._command_regex = re.compile(r'^(sudo webfuzzer|webfuzzer|\.\/webfuzzer)\s+.*?') | |
87 | 85 | self._completition = {'': '__Usage: ./webfuzzer -G|-P URL [OPTIONS]', |
88 | 86 | '-G': '<url> get this as starting url (with parameters)', |
89 | 87 | '-P': '<url> post this as starting url (with parameters)', |
141 | 139 | def processCommandString(self, username, current_path, command_string): |
142 | 140 | """ |
143 | 141 | """ |
142 | super().processCommandString(username, current_path, command_string) | |
144 | 143 | host = re.search("\-([G|P]) ([\w\.\-]+)", command_string) |
145 | ||
146 | 144 | if host is not None: |
147 | 145 | self.host = host.group(2) |
148 | 146 | self._output_path = current_path + "/" + self.host + ".txt" |
4 | 4 | """ |
5 | 5 | import re |
6 | 6 | |
7 | from faraday_plugins.plugins.plugin import PluginBase | |
7 | from faraday_plugins.plugins.plugin import PluginXMLFormat | |
8 | 8 | from faraday_plugins.plugins.plugins_utils import get_vulnweb_url_fields |
9 | 9 | |
10 | 10 | try: |
11 | import xml.etree.ElementTree as ET | |
11 | import xml.etree.cElementTree as ET | |
12 | 12 | except ImportError: |
13 | 13 | import xml.etree.ElementTree as ET |
14 | 14 | |
42 | 42 | |
43 | 43 | def return_text(self, tag,element): |
44 | 44 | try: |
45 | text = element.find(tag).text.encode("ascii", errors="backslashreplace") | |
45 | text = element.find(tag).text | |
46 | 46 | return text |
47 | 47 | except: |
48 | 48 | return "" |
94 | 94 | for section in issue.findall("ReportSection"): |
95 | 95 | |
96 | 96 | try: |
97 | field = section.find("Name").text.encode("ascii", errors="backslashreplace") | |
98 | value = section.find("SectionText").text.encode("ascii", errors="backslashreplace") | |
97 | field = section.find("Name").text | |
98 | value = section.find("SectionText").text | |
99 | 99 | |
100 | 100 | faraday_obj_name = map_objects_fields.get(field)[0] |
101 | 101 | faraday_field = map_objects_fields.get(field)[1] |
111 | 111 | return result |
112 | 112 | |
113 | 113 | |
114 | class WebInspectPlugin(PluginBase): | |
114 | class WebInspectPlugin(PluginXMLFormat): | |
115 | 115 | """ |
116 | 116 | This plugin handles WebInspect reports. |
117 | 117 | """ |
122 | 122 | self.name = "Webinspect" |
123 | 123 | self.plugin_version = "0.0.1" |
124 | 124 | self.version = "1.0.0" |
125 | self.identifier_tag = ["Scan"] | |
125 | 126 | |
126 | def parseOutputString(self, output, debug=False): | |
127 | def parseOutputString(self, output): | |
127 | 128 | |
128 | 129 | parser = WebInspectParser(output) |
129 | 130 | vulns = parser.parse() |
157 | 158 | severity=parser.parse_severity(vuln.get("Vuln").get("severity")) |
158 | 159 | ) |
159 | 160 | |
160 | return True | |
161 | 161 | |
162 | def processCommandString(self, username, current_path, command_string): | |
163 | return None | |
164 | 162 | |
165 | 163 | |
166 | 164 | def createPlugin(): |
18 | 18 | self.port = None |
19 | 19 | self.protocol = None |
20 | 20 | self.fail = None |
21 | self._command_regex = re.compile( | |
22 | r'^(wfuzz).*?') | |
21 | self._command_regex = re.compile(r'^(wfuzz)\s+.*?') | |
23 | 22 | |
24 | 23 | def parseData(self, output): |
25 | 24 | |
79 | 78 | words = item['words'] |
80 | 79 | name = "Wfuzz found: {path} with status {status} on url {url}".format(path=path, status=status, url=url) |
81 | 80 | desc = 'Wfuzz found a response with status {status}. Response contains: \n* {words} words \n* {lines} ' \ |
82 | 'lines \n* {chars} chars'.format(words=words, url=url, lines=lines, chars=chars, status=status) | |
81 | 'lines \n* {chars} chars'.format(words=words, lines=lines, chars=chars, status=status) | |
83 | 82 | self.createAndAddVulnWebToService(host_id, service_id, name, desc, severity="info", website=target, |
84 | 83 | path=path) |
85 | 84 |
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 re | |
7 | import socket | |
8 | import json | |
9 | from faraday_plugins.plugins.plugin import PluginJsonFormat | |
10 | from urllib.parse import urlparse | |
11 | ||
12 | ||
13 | __author__ = "Blas Moyano" | |
14 | __copyright__ = "Copyright (c) 2020, Infobyte LLC" | |
15 | __credits__ = ["Blas Moyano"] | |
16 | __license__ = "" | |
17 | __version__ = "0.0.1" | |
18 | __maintainer__ = "Blas Moyano" | |
19 | __email__ = "[email protected]" | |
20 | __status__ = "Development" | |
21 | ||
22 | ||
23 | class WhitesourcePlugin(PluginJsonFormat): | |
24 | def __init__(self): | |
25 | super().__init__() | |
26 | self.id = "whitesource" | |
27 | self.name = "whitesource" | |
28 | self.plugin_version = "0.1" | |
29 | self.version = "3.4.5" | |
30 | self.json_keys = {"vulnerabilities"} | |
31 | ||
32 | def parseOutputString(self, output, debug=False): | |
33 | parser = json.loads(output) | |
34 | if parser.get('vulnerabilities'): | |
35 | for vulnerability in parser['vulnerabilities']: | |
36 | ||
37 | if 'project' in vulnerability: | |
38 | application_name = vulnerability.get('project') | |
39 | host_id = self.createAndAddHost(application_name) | |
40 | data = '' | |
41 | for key, value in vulnerability['library'].items(): | |
42 | data += f'{key}: {value} \n' | |
43 | refs = [ | |
44 | f"CVSS: {vulnerability['score']}", | |
45 | ] | |
46 | if 'cvss3_score' in vulnerability: | |
47 | refs.append(f"CVSS3: {vulnerability['cvss3_score']}") | |
48 | if 'topFix' in vulnerability: | |
49 | refs.append(f"URL: {vulnerability['topFix']['url']}") | |
50 | self.createAndAddVulnToHost(host_id, | |
51 | name=vulnerability['name'], | |
52 | desc=vulnerability['description'], | |
53 | data=data, | |
54 | resolution=vulnerability['topFix']['fixResolution'], | |
55 | ref=refs, | |
56 | severity=vulnerability['severity']) | |
57 | else: | |
58 | self.createAndAddVulnToHost(host_id, | |
59 | name=vulnerability['name'], | |
60 | desc=vulnerability['description'], | |
61 | data=data, | |
62 | ref=refs, | |
63 | severity=vulnerability['severity']) | |
64 | elif 'namespace' in vulnerability: | |
65 | host_id = self.createAndAddHost(vulnerability['namespace']) | |
66 | service_id = self.createAndAddServiceToHost( | |
67 | host_id, | |
68 | vulnerability['featurename'], | |
69 | ports=0 | |
70 | ) | |
71 | self.createAndAddVulnToService( | |
72 | host_id, | |
73 | service_id, | |
74 | name=vulnerability['vulnerability'], | |
75 | desc=vulnerability['description'], | |
76 | ref=[vulnerability['link']], | |
77 | severity=vulnerability['severity'] | |
78 | ) | |
79 | elif 'package' in vulnerability: | |
80 | host_id = self.createAndAddHost(vulnerability['feed_group']) | |
81 | service_id = self.createAndAddServiceToHost( | |
82 | host_id, | |
83 | vulnerability['package'], | |
84 | ports=0 | |
85 | ) | |
86 | self.createAndAddVulnToService( | |
87 | host_id, | |
88 | service_id, | |
89 | name=f'{vulnerability["vuln"]} {vulnerability["package_name"]}', | |
90 | ref=[vulnerability['url']], | |
91 | severity=vulnerability['severity'] | |
92 | ) | |
93 | ||
94 | ||
95 | ||
96 | ||
97 | ||
98 | def createPlugin(): | |
99 | return WhitesourcePlugin() |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | from faraday_plugins.plugins.plugin import PluginBase | |
7 | 6 | import re |
8 | 7 | import os |
9 | import socket | |
10 | current_path = os.path.abspath(os.getcwd()) | |
8 | ||
9 | from faraday_plugins.plugins.plugin import PluginBase | |
10 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
11 | ||
11 | 12 | |
12 | 13 | __author__ = "Facundo de Guzmán, Esteban Guillardoy" |
13 | 14 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
34 | 35 | self.framework_version = "1.0.0" |
35 | 36 | self.options = None |
36 | 37 | self._current_output = None |
37 | self._command_regex = re.compile(r'^whois.*?') | |
38 | self._command_regex = re.compile(r'^whois\s+.*?') | |
38 | 39 | self._host_ip = None |
39 | 40 | self._info = 0 |
40 | 41 | self._completition = { |
66 | 67 | "--version": "output version information and exit", |
67 | 68 | } |
68 | 69 | |
69 | global current_path | |
70 | 70 | |
71 | def resolve(self, host): | |
72 | try: | |
73 | return socket.gethostbyname(host) | |
74 | except: | |
75 | pass | |
76 | return host | |
77 | 71 | |
78 | 72 | def parseOutputString(self, output, debug=False): |
79 | 73 | matches = re.findall("Name Server:\s*(.*)\s*", output) |
80 | 74 | for m in matches: |
81 | 75 | m = m.strip() |
82 | ip = self.resolve(m) | |
76 | ip = resolve_hostname(m) | |
83 | 77 | h_id = self.createAndAddHost(ip, "os unknown") |
84 | 78 | i_id = self.createAndAddInterface( |
85 | 79 | h_id, ip, "00:00:00:00:00:00", ip, hostname_resolution=[m]) |
86 | 80 | return True |
87 | 81 | |
88 | def processCommandString(self, username, current_path, command_string): | |
89 | """ | |
90 | """ | |
91 | return None | |
92 | 82 | |
93 | 83 | |
94 | 84 | def createPlugin(): |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | |
5 | 5 | """ |
6 | import re | |
7 | import socket | |
8 | 6 | import json |
9 | from faraday_plugins.plugins.plugin import PluginJsonFormat | |
10 | 7 | from urllib.parse import urlparse |
11 | 8 | |
12 | 9 | |
18 | 15 | __maintainer__ = "Nicolas Rebagliati" |
19 | 16 | __email__ = "[email protected]" |
20 | 17 | __status__ = "Development" |
18 | ||
19 | from faraday_plugins.plugins.plugin import PluginJsonFormat | |
20 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
21 | 21 | |
22 | 22 | |
23 | 23 | class WPScanJsonParser: |
38 | 38 | elif protocol == 'http': |
39 | 39 | if not port: |
40 | 40 | port = 80 |
41 | address = self.get_address(hostname) | |
41 | address = resolve_hostname(hostname) | |
42 | 42 | return {'protocol': protocol, 'hostname': hostname, 'port': port, 'address': address} |
43 | 43 | |
44 | def get_address(self, hostname): | |
45 | # Returns remote IP address from hostname. | |
46 | try: | |
47 | return socket.gethostbyname(hostname) | |
48 | except socket.error as msg: | |
49 | return None | |
50 | 44 | |
51 | 45 | |
52 | 46 | class WPScanPlugin(PluginJsonFormat): |
164 | 164 | self.framework_version = "1.0.0" |
165 | 165 | self.options = None |
166 | 166 | self._current_output = None |
167 | self._command_regex = re.compile(r'^(sudo x1|\.\/x1).*?') | |
167 | self._command_regex = re.compile(r'^(sudo x1|\.\/x1)\s+.*?') | |
168 | 168 | |
169 | 169 | |
170 | 170 | |
191 | 191 | |
192 | 192 | del parser |
193 | 193 | |
194 | def processCommandString(self, username, current_path, command_string): | |
195 | return None | |
196 | 194 | |
197 | 195 | def setHost(self): |
198 | 196 | pass |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | 5 | import re |
6 | import socket | |
7 | from faraday_plugins.plugins.plugin import PluginBase | |
8 | 6 | |
9 | 7 | __author__ = "Roberto Focke" |
10 | 8 | __copyright__ = "Copyright (c) 2017, Infobyte LLC" |
11 | 9 | __license__ = "" |
12 | 10 | __version__ = "1.0.0" |
11 | ||
12 | from faraday_plugins.plugins.plugin import PluginBase | |
13 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
13 | 14 | |
14 | 15 | |
15 | 16 | class xsssniper(PluginBase): |
20 | 21 | self.name = "xsssniper" |
21 | 22 | self.plugin_version = "0.0.1" |
22 | 23 | self.version = "1.0.0" |
23 | self.protocol="tcp" | |
24 | self._command_regex = re.compile(r'^(sudo xsssniper|xsssniper|sudo xsssniper\.py|xsssniper\.py|sudo python ' | |
25 | r'xsssniper\.py|.\/xsssniper\.py|python xsssniper\.py)') | |
24 | self.protocol = "tcp" | |
25 | self._command_regex = re.compile(r'^(sudo xsssniper|xsssniper|sudo xsssniper\.py|xsssniper\.py|sudo python' | |
26 | r'xsssniper\.py|.\/xsssniper\.py|python xsssniper\.py)\s+') | |
26 | 27 | |
27 | 28 | def parseOutputString(self, output, debug=False): |
28 | 29 | parametro = [] |
34 | 35 | linea = linea.lower() |
35 | 36 | if ((linea.find("target:")>0)): |
36 | 37 | url = re.findall('(?:[-\w.]|(?:%[\da-fA-F]{2}))+', linea) |
38 | print(url) | |
37 | 39 | host_id = self.createAndAddHost(url[3]) |
38 | address=socket.gethostbyname(url[3]) | |
40 | address = resolve_hostname(url[3]) | |
39 | 41 | interface_id = self.createAndAddInterface(host_id,address,ipv4_address=address,hostname_resolution=url[3]) |
40 | 42 | if ((linea.find("method")>0)): |
41 | 43 | list_a = re.findall("\w+", linea) |
53 | 55 | website=url[0], path='', method=metodo, pname='', |
54 | 56 | params=''.join(parametro), request='', response='') |
55 | 57 | |
56 | def processCommandString(self, username, current_path, command_string): | |
57 | return None | |
58 | ||
59 | 58 | |
60 | 59 | def createPlugin(): |
61 | 60 | return xsssniper() |
3 | 3 | See the file 'doc/LICENSE' for the license information |
4 | 4 | """ |
5 | 5 | import re |
6 | import os | |
7 | import socket | |
6 | from urllib.parse import urlparse | |
8 | 7 | from faraday_plugins.plugins.plugin import PluginXMLFormat |
9 | from urllib.parse import urlparse | |
8 | from faraday_plugins.plugins.plugins_utils import resolve_hostname | |
10 | 9 | |
11 | 10 | try: |
12 | 11 | import xml.etree.cElementTree as ET |
17 | 16 | |
18 | 17 | ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")] |
19 | 18 | |
20 | current_path = os.path.abspath(os.getcwd()) | |
21 | 19 | |
22 | 20 | __author__ = "Francisco Amato" |
23 | 21 | __copyright__ = "Copyright (c) 2013, Infobyte LLC" |
29 | 27 | __status__ = "Development" |
30 | 28 | |
31 | 29 | |
32 | class ParserEtToAscii(ET.TreeBuilder): | |
33 | def __init__(self, *args, **kwargs): | |
34 | super().__init__(*args, **kwargs) | |
35 | print(self._data) | |
36 | ||
37 | def data(self, data): | |
38 | self._data.append(data.encode("ascii", errors="backslashreplace")) | |
39 | ||
40 | 30 | |
41 | 31 | class ZapXmlParser: |
42 | 32 | """ |
134 | 124 | self.node = item_node |
135 | 125 | |
136 | 126 | self.host = self.node.get('host') |
137 | self.ip = self.resolve(self.host) | |
127 | self.ip = resolve_hostname(self.host) | |
138 | 128 | self.port = self.node.get('port') |
139 | 129 | |
140 | 130 | self.items = [] |
152 | 142 | return sub_node.text |
153 | 143 | return None |
154 | 144 | |
155 | def resolve(self, host): | |
156 | ||
157 | try: | |
158 | return socket.gethostbyname(host) | |
159 | except: | |
160 | pass | |
161 | ||
162 | return host | |
163 | 145 | |
164 | 146 | |
165 | 147 | class Item: |
253 | 235 | self.version = "2.4.3" |
254 | 236 | self.framework_version = "1.0.0" |
255 | 237 | self.options = None |
256 | self._current_output = None | |
257 | self.target = None | |
258 | self._command_regex = re.compile(r'^(zap|sudo zap|\.\/zap).*?') | |
238 | ||
259 | 239 | |
260 | 240 | def parseOutputString(self, output, debug=False): |
261 | 241 | """ |
309 | 289 | |
310 | 290 | del parser |
311 | 291 | |
312 | def processCommandString(self, username, current_path, command_string): | |
313 | return None | |
314 | 292 | |
315 | 293 | def setHost(self): |
316 | 294 | pass |
11 | 11 | 'lxml', |
12 | 12 | 'html2text', |
13 | 13 | 'beautifulsoup4', |
14 | 'pytz', | |
15 | 'python-dateutil', | |
16 | 'colorama' | |
14 | 17 | ] |
15 | 18 | |
16 | 19 | |
21 | 24 | url='', |
22 | 25 | license='', |
23 | 26 | author='Faradaysec', |
24 | author_email='', | |
25 | description='', | |
27 | author_email='[email protected]', | |
28 | description='Faraday plugins package', | |
26 | 29 | include_package_data=True, |
27 | 30 | install_requires=install_requires, |
31 | entry_points={ | |
32 | 'console_scripts': [ | |
33 | 'faraday-plugins=faraday_plugins.commands:cli', | |
34 | ], | |
35 | }, | |
28 | 36 | ) |
0 | { | |
1 | "commands": [ | |
2 | {"plugin_id": "ping", "command": "ping -c4 faradaysec.com"}, | |
3 | {"plugin_id": "whois", "command": "whois fradaysec.com"}, | |
4 | {"plugin_id": "nmap", "command": "nmap fradaysec.com"}, | |
5 | {"plugin_id": "skipfish", "command": "skipfish http://fradaysec.com"} | |
6 | ] | |
7 | }⏎ |
0 | import pytest | |
1 | ||
2 | ||
3 | def pytest_addoption(parser): | |
4 | parser.addoption( | |
5 | "--performance", action="store_true", default=False, help="run performance tests" | |
6 | ) | |
7 | ||
8 | ||
9 | def pytest_configure(config): | |
10 | config.addinivalue_line("markers", "performance: mark test as performance") | |
11 | ||
12 | ||
13 | def pytest_collection_modifyitems(config, items): | |
14 | if config.getoption("--performance"): | |
15 | # --performance given in cli: do not skip performance tests | |
16 | return | |
17 | performance = pytest.mark.skip(reason="need --performance option to run") | |
18 | for item in items: | |
19 | if "performance" in item.keywords: | |
20 | item.add_marker(performance)⏎ |
0 | #!/usr/bin/env python | |
1 | import hashlib | |
2 | import os | |
3 | import shutil | |
4 | import json | |
5 | import click | |
6 | import colorama | |
7 | from faraday_plugins.plugins.manager import PluginsManager, ReportAnalyzer | |
8 | from faraday_plugins.plugins.plugin import PluginBase | |
9 | ||
10 | colorama.init(autoreset=True) | |
11 | ||
12 | BLACK_LIST = [ | |
13 | 'LICENSE', | |
14 | 'README.md', | |
15 | '.gitignore', | |
16 | '.gitkeep', | |
17 | 'faraday_plugins_tests', | |
18 | ] | |
19 | ||
20 | REPORT_COLLECTION_DIR = '../report-collection' | |
21 | FARADAY_PLUGINS_TESTS_DIR = 'faraday_plugins_tests' | |
22 | REPORTS_CHECKSUM = [] | |
23 | ||
24 | def list_report_files(): | |
25 | report_filenames = os.walk(os.path.join(REPORT_COLLECTION_DIR)) | |
26 | for root, directory, filenames in report_filenames: | |
27 | if '.git' in directory or FARADAY_PLUGINS_TESTS_DIR in root: | |
28 | continue | |
29 | for filename in filenames: | |
30 | if filename in BLACK_LIST: | |
31 | continue | |
32 | if '.git' in root: | |
33 | continue | |
34 | yield os.path.join(root, filename) | |
35 | ||
36 | ||
37 | @click.command() | |
38 | @click.option('--force', is_flag=True) | |
39 | def generate_reports_tests(force): | |
40 | generated_summaries = 0 | |
41 | analysed_reports = 0 | |
42 | click.echo(f"{colorama.Fore.GREEN}Generate Faraday Plugins Tests Summary") | |
43 | plugins_manager = PluginsManager() | |
44 | analyzer = ReportAnalyzer(plugins_manager) | |
45 | for report_file_path in list_report_files(): | |
46 | plugin: PluginBase = analyzer.get_plugin(report_file_path) | |
47 | if not plugin: | |
48 | click.echo(f"{colorama.Fore.YELLOW}Plugin for file: ({report_file_path}) not found") | |
49 | else: | |
50 | with open(report_file_path, 'rb') as f: | |
51 | m = hashlib.md5(f.read()) | |
52 | file_checksum = m.hexdigest() | |
53 | if file_checksum not in REPORTS_CHECKSUM: | |
54 | REPORTS_CHECKSUM.append(file_checksum) | |
55 | else: | |
56 | click.echo(f"{colorama.Fore.YELLOW}Ignore duplicated file: ({report_file_path})") | |
57 | continue | |
58 | analysed_reports += 1 | |
59 | report_file_name = os.path.basename(report_file_path) | |
60 | plugin_name = plugin.id | |
61 | plugin_path = os.path.join(REPORT_COLLECTION_DIR, FARADAY_PLUGINS_TESTS_DIR, plugin_name) | |
62 | if not os.path.isdir(plugin_path): | |
63 | os.mkdir(plugin_path) | |
64 | dst_report_file_path = os.path.join(plugin_path, report_file_name) | |
65 | summary_needed = False | |
66 | summary_file = f"{os.path.splitext(dst_report_file_path)[0]}_summary.json" | |
67 | if not os.path.isfile(dst_report_file_path) or force: | |
68 | summary_needed = True | |
69 | shutil.copyfile(report_file_path, dst_report_file_path) | |
70 | if not os.path.isfile(summary_file) or force: | |
71 | summary_needed = True | |
72 | if summary_needed: | |
73 | try: | |
74 | plugin.processReport(report_file_path) | |
75 | click.echo(f"{colorama.Fore.GREEN}Generate Summary for: {dst_report_file_path} [{plugin}]") | |
76 | summary = plugin.get_summary() | |
77 | with open(summary_file, "w") as f: | |
78 | json.dump(summary, f) | |
79 | generated_summaries += 1 | |
80 | except Exception as e: | |
81 | click.echo(f"{colorama.Fore.RED}Error generating summary for file: {report_file_path} [{plugin}]: [{e}]") | |
82 | click.echo(f"Generated {generated_summaries} summaries of {analysed_reports} reports") | |
83 | ||
84 | ||
85 | if __name__ == "__main__": | |
86 | generate_reports_tests() |
0 | import json | |
1 | import pytest | |
2 | from faraday_plugins.plugins.manager import PluginsManager, CommandAnalyzer | |
3 | from faraday_plugins.plugins.plugin import PluginBase | |
4 | ||
5 | ||
6 | plugins_manager = PluginsManager() | |
7 | analyzer = CommandAnalyzer(plugins_manager) | |
8 | ||
9 | COMMANDS_FILE = './tests/commands.json' | |
10 | ||
11 | def list_commands(): | |
12 | with open(COMMANDS_FILE) as f: | |
13 | commands_dict = json.load(f) | |
14 | for command_data in commands_dict["commands"]: | |
15 | yield command_data | |
16 | ||
17 | ||
18 | @pytest.mark.parametrize("command_data", list_commands()) | |
19 | def test_autodetected_on_commands(command_data): | |
20 | plugin_id = command_data["plugin_id"] | |
21 | command_string = command_data["command"] | |
22 | plugin: PluginBase = analyzer.get_plugin(command_string) | |
23 | assert plugin, command_string | |
24 | assert plugin.id.lower() == plugin_id.lower() | |
25 |
0 | from faraday_plugins.plugins.plugin import PluginBase | |
1 | ||
2 | ||
3 | def test_get_host_cache_id_with_same_host(): | |
4 | host_1 = {'ip': '127.0.0.1'} | |
5 | host_2 = {'ip': '127.0.0.1', 'description': 'test desc'} | |
6 | cache_id_1 = PluginBase.get_host_cache_id(host_1) | |
7 | cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
8 | ||
9 | assert cache_id_1 == cache_id_2 | |
10 | ||
11 | ||
12 | def test_get_host_cache_id_with_diffent_ip(): | |
13 | host_1 = {'ip': '127.0.0.1'} | |
14 | host_2 = {'ip': '192.168.0.1', 'description': 'test desc'} | |
15 | cache_id_1 = PluginBase.get_host_cache_id(host_1) | |
16 | cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
17 | ||
18 | assert cache_id_1 != cache_id_2 | |
19 | ||
20 | ||
21 | def test_get_host_service_cache_id_same_objects(): | |
22 | host_1 = {'ip': '127.0.0.1'} | |
23 | host_cache_id_1 = PluginBase.get_host_cache_id(host_1) | |
24 | service_1 = {'protocol': 'tcp', 'port': 80} | |
25 | host_2 = {'ip': '127.0.0.1'} | |
26 | host_cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
27 | service_2 = {'protocol': 'tcp', 'port': 80} | |
28 | ||
29 | cache_1 = PluginBase.get_host_service_cache_id(host_cache_id_1, service_1) | |
30 | cache_2 = PluginBase.get_host_service_cache_id(host_cache_id_2, service_2) | |
31 | ||
32 | assert cache_1 == cache_2 | |
33 | ||
34 | ||
35 | def test_get_host_service_cache_id_different_host(): | |
36 | host_1 = {'ip': '127.0.0.1'} | |
37 | host_cache_id_1 = PluginBase.get_host_cache_id(host_1) | |
38 | service_1 = {'protocol': 'tcp', 'port': 80} | |
39 | host_2 = {'ip': '192.168.0.1'} | |
40 | host_cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
41 | service_2 = {'protocol': 'tcp', 'port': 80} | |
42 | ||
43 | cache_1 = PluginBase.get_host_service_cache_id(host_cache_id_1, service_1) | |
44 | cache_2 = PluginBase.get_host_service_cache_id(host_cache_id_2, service_2) | |
45 | ||
46 | assert cache_1 != cache_2 | |
47 | ||
48 | ||
49 | def test_get_host_vuln_cache_id_severity_does_not_affect_duplicate(): | |
50 | host_1 = {'ip': '127.0.0.1'} | |
51 | host_cache_id_1 = PluginBase.get_host_cache_id(host_1) | |
52 | vuln_1 = {'name': 'test', 'desc': 'test', 'severity': 'low'} | |
53 | ||
54 | host_2 = {'ip': '127.0.0.1'} | |
55 | host_cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
56 | vuln_2 = {'name': 'test', 'desc': 'test', 'severity': 'high'} | |
57 | ||
58 | ||
59 | cache_1 = PluginBase.get_host_vuln_cache_id(host_cache_id_1, vuln_1) | |
60 | cache_2 = PluginBase.get_host_vuln_cache_id(host_cache_id_2, vuln_2) | |
61 | ||
62 | assert cache_1 == cache_2 | |
63 | ||
64 | ||
65 | def test_get_host_vuln_cache_id_description_makes_different_cache_ids(): | |
66 | host_1 = {'ip': '127.0.0.1'} | |
67 | host_cache_id_1 = PluginBase.get_host_cache_id(host_1) | |
68 | vuln_1 = {'name': 'test', 'desc': 'test', 'severity': 'low'} | |
69 | ||
70 | host_2 = {'ip': '127.0.0.1'} | |
71 | host_cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
72 | vuln_2 = {'name': 'test', 'new desc': 'test', 'severity': 'high'} | |
73 | ||
74 | ||
75 | cache_1 = PluginBase.get_host_vuln_cache_id(host_cache_id_1, vuln_1) | |
76 | cache_2 = PluginBase.get_host_vuln_cache_id(host_cache_id_2, vuln_2) | |
77 | ||
78 | assert cache_1 != cache_2 | |
79 | ||
80 | ||
81 | def test_get_service_vuln_cache_id_severity_does_not_affect_cache_id(): | |
82 | host_1 = {'ip': '127.0.0.1'} | |
83 | host_cache_id_1 = PluginBase.get_host_cache_id(host_1) | |
84 | service_1 = {'protocol': 'tcp', 'port': 80} | |
85 | ||
86 | host_2 = {'ip': '127.0.0.1'} | |
87 | host_cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
88 | host_2 = {'ip': '127.0.0.1'} | |
89 | host_cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
90 | service_2 = {'protocol': 'tcp', 'port': 80} | |
91 | ||
92 | service_cache_1 = PluginBase.get_host_service_cache_id(host_cache_id_1, service_1) | |
93 | service_cache_2 = PluginBase.get_host_service_cache_id(host_cache_id_2, service_2) | |
94 | ||
95 | vuln_2 = {'name': 'test', 'desc': 'test', 'severity': 'high', 'method': 'GET'} | |
96 | vuln_1 = {'name': 'test', 'desc': 'test', 'severity': 'low', 'method': 'GET'} | |
97 | ||
98 | cache_1 = PluginBase.get_service_vuln_cache_id(host_cache_id_1, service_cache_1, vuln_1) | |
99 | cache_2 = PluginBase.get_service_vuln_cache_id(host_cache_id_2, service_cache_2, vuln_2) | |
100 | ||
101 | assert cache_1 == cache_2 | |
102 | ||
103 | def test_get_service_vuln_cache_id_with_different_service_return_different_id(): | |
104 | host_1 = {'ip': '127.0.0.1'} | |
105 | host_cache_id_1 = PluginBase.get_host_cache_id(host_1) | |
106 | service_1 = {'protocol': 'tcp', 'port': 80} | |
107 | ||
108 | host_2 = {'ip': '127.0.0.1'} | |
109 | host_cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
110 | host_2 = {'ip': '127.0.0.1'} | |
111 | host_cache_id_2 = PluginBase.get_host_cache_id(host_2) | |
112 | service_2 = {'protocol': 'tcp', 'port': 22} | |
113 | ||
114 | service_cache_1 = PluginBase.get_host_service_cache_id(host_cache_id_1, service_1) | |
115 | service_cache_2 = PluginBase.get_host_service_cache_id(host_cache_id_2, service_2) | |
116 | ||
117 | vuln_2 = {'name': 'test', 'desc': 'test', 'severity': 'high', 'method': 'GET'} | |
118 | vuln_1 = {'name': 'test', 'desc': 'test', 'severity': 'low', 'method': 'GET'} | |
119 | ||
120 | cache_1 = PluginBase.get_service_vuln_cache_id(host_cache_id_1, service_cache_1, vuln_1) | |
121 | cache_2 = PluginBase.get_service_vuln_cache_id(host_cache_id_2, service_cache_2, vuln_2) | |
122 | ||
123 | assert cache_1 != cache_2⏎ |
0 | 0 | import os |
1 | ||
1 | import socket | |
2 | 2 | import json |
3 | 3 | import pytest |
4 | 4 | from faraday_plugins.plugins.manager import PluginsManager, ReportAnalyzer |
5 | 5 | from faraday_plugins.plugins.plugin import PluginBase |
6 | from faraday.server.api.modules.bulk_create import BulkCreateSchema | |
6 | 7 | |
7 | 8 | BLACK_LIST = [ |
8 | 9 | 'LICENSE', |
9 | 10 | 'README.md', |
10 | 11 | '.gitignore', |
11 | 12 | '.gitkeep', |
13 | 'faraday_plugins_tests', | |
14 | ||
12 | 15 | ] |
13 | 16 | |
17 | plugins_manager = PluginsManager() | |
18 | analyzer = ReportAnalyzer(plugins_manager) | |
19 | ||
20 | PLUGINS_CACHE = {} | |
21 | REPORTS_JSON_CACHE = {} | |
22 | ||
23 | SKIP_IP_PLUGINS = ['Fortify'] | |
24 | ||
25 | REPORTS_SUMMARY_DIR = './report-collection/faraday_plugins_tests' | |
26 | ||
27 | ||
28 | def get_plugin_from_cache(report_file): | |
29 | plugin = PLUGINS_CACHE.get(report_file) | |
30 | if not plugin: | |
31 | plugin: PluginBase = analyzer.get_plugin(report_file) | |
32 | if plugin: | |
33 | save_plugin_in_cache(report_file, plugin) | |
34 | return plugin | |
35 | ||
36 | ||
37 | def save_plugin_in_cache(report_file, plugin): | |
38 | if report_file not in PLUGINS_CACHE: | |
39 | PLUGINS_CACHE[report_file] = plugin | |
40 | ||
41 | ||
42 | def get_report_json_from_cache(report_file): | |
43 | plugin_json = REPORTS_JSON_CACHE.get(report_file) | |
44 | if not plugin_json: | |
45 | plugin = get_plugin_from_cache(report_file) | |
46 | if plugin: | |
47 | plugin.processReport(report_file) | |
48 | plugin_json = json.loads(plugin.get_json()) | |
49 | REPORTS_JSON_CACHE[report_file] = plugin_json | |
50 | else: | |
51 | plugin = get_plugin_from_cache(report_file) | |
52 | return plugin, plugin_json | |
53 | ||
54 | ||
14 | 55 | def list_report_files(): |
15 | report_filenames = os.walk('./report-collection') | |
16 | ||
56 | report_filenames = os.walk(REPORTS_SUMMARY_DIR) | |
17 | 57 | for root, directory, filenames in report_filenames: |
18 | if '.git' in directory: | |
58 | if '.git' in directory or 'faraday_plugins_tests' in directory: | |
19 | 59 | continue |
20 | 60 | for filename in filenames: |
21 | 61 | if filename in BLACK_LIST: |
22 | 62 | continue |
23 | 63 | if '.git' in root: |
24 | 64 | continue |
25 | yield os.path.join(root, filename) | |
65 | if not filename.endswith('_summary.json'): | |
66 | yield os.path.join(root, filename) | |
26 | 67 | |
27 | 68 | |
28 | @pytest.mark.skip(reason="Skip auto detection test until we review all the report files") | |
69 | def is_valid_ipv4_address(address): | |
70 | try: | |
71 | socket.inet_pton(socket.AF_INET, address) | |
72 | except AttributeError: # no inet_pton here, sorry | |
73 | try: | |
74 | socket.inet_aton(address) | |
75 | except socket.error: | |
76 | return False | |
77 | return address.count('.') == 3 | |
78 | except socket.error: # not a valid address | |
79 | return False | |
80 | return True | |
81 | ||
82 | ||
83 | def is_valid_ipv6_address(address): | |
84 | try: | |
85 | socket.inet_pton(socket.AF_INET6, address) | |
86 | except socket.error: # not a valid address | |
87 | return False | |
88 | return True | |
89 | ||
90 | ||
91 | def is_valid_ip_address(address): | |
92 | return (is_valid_ipv4_address(address) or is_valid_ipv6_address(address)) | |
93 | ||
94 | def test_reports_collection_exists(): | |
95 | assert os.path.isdir(REPORTS_SUMMARY_DIR) is True, "Please clone the report-collection repo!" | |
96 | ||
29 | 97 | @pytest.mark.parametrize("report_filename", list_report_files()) |
30 | 98 | def test_autodetected_on_all_report_collection(report_filename): |
31 | plugins_manager = PluginsManager() | |
32 | analyzer = ReportAnalyzer(plugins_manager) | |
33 | plugin: PluginBase = analyzer.get_plugin(report_filename) | |
99 | plugin: PluginBase = get_plugin_from_cache(report_filename) | |
34 | 100 | assert plugin, report_filename |
35 | 101 | |
36 | 102 | |
37 | 103 | @pytest.mark.parametrize("report_filename", list_report_files()) |
38 | def test_detected_tools_on_all_report_collection(report_filename): | |
104 | def test_schema_on_all_reports(report_filename): | |
105 | plugin, plugin_json = get_report_json_from_cache(report_filename) | |
106 | if plugin_json: | |
107 | serializer = BulkCreateSchema() | |
108 | res = serializer.loads(json.dumps(plugin_json)) | |
109 | assert not res.errors | |
110 | ||
111 | ||
112 | @pytest.mark.skip(reason="Skip validate ip format") | |
113 | @pytest.mark.parametrize("report_filename", list_report_files()) | |
114 | def test_host_ips_all_reports(report_filename): | |
115 | plugin, plugin_json = get_report_json_from_cache(report_filename) | |
116 | if plugin_json: | |
117 | if plugin.id not in SKIP_IP_PLUGINS: | |
118 | for host in plugin_json['hosts']: | |
119 | assert is_valid_ip_address(host['ip']) is True | |
120 | ||
121 | ||
122 | @pytest.mark.parametrize("report_filename", list_report_files()) | |
123 | def test_summary_reports(report_filename): | |
124 | plugin, plugin_json = get_report_json_from_cache(report_filename) | |
125 | if plugin_json: | |
126 | summary_file = f"{os.path.splitext(report_filename)[0]}_summary.json" | |
127 | assert os.path.isfile(summary_file) is True | |
128 | with open(summary_file) as f: | |
129 | saved_summary = json.load(f) | |
130 | summary = plugin.get_summary() | |
131 | vuln_hashes = set(summary['vuln_hashes']) | |
132 | saved_vuln_hashes = set(saved_summary.get('vuln_hashes', [])) | |
133 | assert summary['hosts'] == saved_summary['hosts'] | |
134 | assert summary['services'] == saved_summary['services'] | |
135 | assert summary['hosts_vulns'] == saved_summary['hosts_vulns'] | |
136 | assert summary['services_vulns'] == saved_summary['services_vulns'] | |
137 | assert summary['severity_vulns'] == saved_summary['severity_vulns'] | |
138 | assert vuln_hashes == saved_vuln_hashes | |
139 | ||
140 | ||
141 | @pytest.mark.performance | |
142 | @pytest.mark.parametrize("report_filename", list_report_files()) | |
143 | def test_detected_tools_on_all_report_collection(report_filename, benchmark): | |
39 | 144 | plugins_manager = PluginsManager() |
40 | 145 | analyzer = ReportAnalyzer(plugins_manager) |
41 | 146 | plugin: PluginBase = analyzer.get_plugin(report_filename) |
42 | 147 | if not plugin: |
43 | 148 | return |
44 | 149 | assert plugin, report_filename |
45 | plugin.processReport(report_filename) | |
150 | benchmark(plugin.processReport, report_filename) | |
46 | 151 | plugin_json = json.loads(plugin.get_json()) |
47 | 152 | assert "hosts" in plugin_json |
48 | 153 | assert "command" in plugin_json |
49 | assert len(plugin_json) == 2 | |
154 | assert os.path.isfile(report_filename) is True | |
155 |